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

package io.netty.handler.codec.quic;

import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.ChannelPromise;
import java.net.SocketAddress;
import io.netty.channel.Channel;
import io.netty.util.CharsetUtil;
import io.netty.channel.socket.DatagramPacket;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.util.function.Consumer;
import java.net.InetSocketAddress;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.buffer.ByteBuf;
import io.netty.util.AttributeKey;
import io.netty.channel.ChannelOption;
import java.util.Map;
import io.netty.channel.ChannelHandler;
import java.util.concurrent.Executor;
import java.util.function.Function;
import io.netty.util.internal.logging.InternalLogger;

final class QuicheQuicServerCodec extends QuicheQuicCodec
{
    private static final InternalLogger LOGGER;
    private final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider;
    private final Executor sslTaskExecutor;
    private final QuicConnectionIdGenerator connectionIdAddressGenerator;
    private final QuicResetTokenGenerator resetTokenGenerator;
    private final QuicTokenHandler tokenHandler;
    private final ChannelHandler handler;
    private final Map.Entry<ChannelOption<?>, Object>[] optionsArray;
    private final Map.Entry<AttributeKey<?>, Object>[] attrsArray;
    private final ChannelHandler streamHandler;
    private final Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray;
    private final Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray;
    private ByteBuf mintTokenBuffer;
    private ByteBuf connIdBuffer;
    
    QuicheQuicServerCodec(final QuicheConfig config, final int localConnIdLength, final QuicTokenHandler tokenHandler, final QuicConnectionIdGenerator connectionIdAddressGenerator, final QuicResetTokenGenerator resetTokenGenerator, final FlushStrategy flushStrategy, final Function<QuicChannel, ? extends QuicSslEngine> sslEngineProvider, final Executor sslTaskExecutor, final ChannelHandler handler, final Map.Entry<ChannelOption<?>, Object>[] optionsArray, final Map.Entry<AttributeKey<?>, Object>[] attrsArray, final ChannelHandler streamHandler, final Map.Entry<ChannelOption<?>, Object>[] streamOptionsArray, final Map.Entry<AttributeKey<?>, Object>[] streamAttrsArray) {
        super(config, localConnIdLength, flushStrategy);
        this.tokenHandler = tokenHandler;
        this.connectionIdAddressGenerator = connectionIdAddressGenerator;
        this.resetTokenGenerator = resetTokenGenerator;
        this.sslEngineProvider = sslEngineProvider;
        this.sslTaskExecutor = sslTaskExecutor;
        this.handler = handler;
        this.optionsArray = optionsArray;
        this.attrsArray = attrsArray;
        this.streamHandler = streamHandler;
        this.streamOptionsArray = streamOptionsArray;
        this.streamAttrsArray = streamAttrsArray;
    }
    
    @Override
    protected void handlerAdded(final ChannelHandlerContext ctx, final int localConnIdLength) {
        this.connIdBuffer = Quiche.allocateNativeOrder(localConnIdLength);
        this.mintTokenBuffer = Unpooled.directBuffer(this.tokenHandler.maxTokenLength());
    }
    
    @Override
    public void handlerRemoved(final ChannelHandlerContext ctx) {
        super.handlerRemoved(ctx);
        if (this.connIdBuffer != null) {
            this.connIdBuffer.release();
        }
        if (this.mintTokenBuffer != null) {
            this.mintTokenBuffer.release();
        }
    }
    
    @Nullable
    @Override
    protected QuicheQuicChannel quicPacketRead(final ChannelHandlerContext ctx, final InetSocketAddress sender, final InetSocketAddress recipient, final QuicPacketType type, final long version, final ByteBuf scid, final ByteBuf dcid, final ByteBuf token, final ByteBuf senderSockaddrMemory, final ByteBuf recipientSockaddrMemory, final Consumer<QuicheQuicChannel> freeTask, final int localConnIdLength, final QuicheConfig config) throws Exception {
        final ByteBuffer dcidByteBuffer = dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes());
        final QuicheQuicChannel channel = this.getChannel(dcidByteBuffer);
        if (channel == null && type == QuicPacketType.INITIAL) {
            return this.handleServer(ctx, sender, recipient, type, version, scid, dcid, token, senderSockaddrMemory, recipientSockaddrMemory, freeTask, localConnIdLength, config);
        }
        return channel;
    }
    
    private static void writePacket(final ChannelHandlerContext ctx, final int res, final ByteBuf buffer, final InetSocketAddress sender) throws Exception {
        if (res < 0) {
            buffer.release();
            if (res != Quiche.QUICHE_ERR_DONE) {
                throw Quiche.convertToException(res);
            }
        }
        else {
            ctx.writeAndFlush(new DatagramPacket(buffer.writerIndex(buffer.writerIndex() + res), sender));
        }
    }
    
    @Nullable
    private QuicheQuicChannel handleServer(final ChannelHandlerContext ctx, final InetSocketAddress sender, final InetSocketAddress recipient, final QuicPacketType type, final long version, final ByteBuf scid, final ByteBuf dcid, final ByteBuf token, final ByteBuf senderSockaddrMemory, final ByteBuf recipientSockaddrMemory, final Consumer<QuicheQuicChannel> freeTask, final int localConnIdLength, final QuicheConfig config) throws Exception {
        if (!Quiche.quiche_version_is_supported((int)version)) {
            final ByteBuf out = ctx.alloc().directBuffer(1350);
            final int res = Quiche.quiche_negotiate_version(Quiche.readerMemoryAddress(scid), scid.readableBytes(), Quiche.readerMemoryAddress(dcid), dcid.readableBytes(), Quiche.writerMemoryAddress(out), out.writableBytes());
            writePacket(ctx, res, out, sender);
            return null;
        }
        boolean noToken = false;
        int offset;
        if (!token.isReadable()) {
            this.mintTokenBuffer.clear();
            this.connIdBuffer.clear();
            if (this.tokenHandler.writeToken(this.mintTokenBuffer, dcid, sender)) {
                final ByteBuffer connId = this.connectionIdAddressGenerator.newId(scid.internalNioBuffer(scid.readerIndex(), scid.readableBytes()), dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
                this.connIdBuffer.writeBytes(connId);
                final ByteBuf out2 = ctx.alloc().directBuffer(1350);
                final int written = Quiche.quiche_retry(Quiche.readerMemoryAddress(scid), scid.readableBytes(), Quiche.readerMemoryAddress(dcid), dcid.readableBytes(), Quiche.readerMemoryAddress(this.connIdBuffer), this.connIdBuffer.readableBytes(), Quiche.readerMemoryAddress(this.mintTokenBuffer), this.mintTokenBuffer.readableBytes(), (int)version, Quiche.writerMemoryAddress(out2), out2.writableBytes());
                writePacket(ctx, written, out2, sender);
                return null;
            }
            offset = 0;
            noToken = true;
        }
        else {
            offset = this.tokenHandler.validateToken(token.slice(), sender);
            if (offset == -1) {
                if (QuicheQuicServerCodec.LOGGER.isDebugEnabled()) {
                    QuicheQuicServerCodec.LOGGER.debug("invalid token: {}", token.toString(CharsetUtil.US_ASCII));
                }
                return null;
            }
        }
        ByteBuffer key;
        long scidAddr;
        int scidLen;
        long ocidAddr;
        int ocidLen;
        if (noToken) {
            this.connIdBuffer.clear();
            key = this.connectionIdAddressGenerator.newId(scid.internalNioBuffer(scid.readerIndex(), scid.readableBytes()), dcid.internalNioBuffer(dcid.readerIndex(), dcid.readableBytes()), localConnIdLength);
            this.connIdBuffer.writeBytes(key.duplicate());
            scidAddr = Quiche.readerMemoryAddress(this.connIdBuffer);
            scidLen = localConnIdLength;
            ocidAddr = -1L;
            ocidLen = -1;
            final QuicheQuicChannel existingChannel = this.getChannel(key);
            if (existingChannel != null) {
                return existingChannel;
            }
        }
        else {
            scidAddr = Quiche.readerMemoryAddress(dcid);
            scidLen = localConnIdLength;
            ocidLen = token.readableBytes() - offset;
            ocidAddr = Quiche.memoryAddress(token, offset, ocidLen);
            final byte[] bytes = new byte[localConnIdLength];
            dcid.getBytes(dcid.readerIndex(), bytes);
            key = ByteBuffer.wrap(bytes);
        }
        final QuicheQuicChannel channel = QuicheQuicChannel.forServer(ctx.channel(), key, recipient, sender, config.isDatagramSupported(), this.streamHandler, this.streamOptionsArray, this.streamAttrsArray, freeTask, this.sslTaskExecutor, this.connectionIdAddressGenerator, this.resetTokenGenerator);
        final byte[] originalId = new byte[dcid.readableBytes()];
        dcid.getBytes(dcid.readerIndex(), originalId);
        channel.sourceConnectionIds().add(ByteBuffer.wrap(originalId));
        Quic.setupChannel(channel, this.optionsArray, this.attrsArray, this.handler, QuicheQuicServerCodec.LOGGER);
        final QuicSslEngine engine = (QuicSslEngine)this.sslEngineProvider.apply(channel);
        if (!(engine instanceof QuicheQuicSslEngine)) {
            channel.unsafe().closeForcibly();
            throw new IllegalArgumentException("QuicSslEngine is not of type " + QuicheQuicSslEngine.class.getSimpleName());
        }
        if (engine.getUseClientMode()) {
            channel.unsafe().closeForcibly();
            throw new IllegalArgumentException("QuicSslEngine is not created in server mode");
        }
        final QuicheQuicSslEngine quicSslEngine = (QuicheQuicSslEngine)engine;
        final QuicheQuicConnection connection = quicSslEngine.createConnection(ssl -> {
            final ByteBuffer localAddrMemory = recipientSockaddrMemory.internalNioBuffer(0, recipientSockaddrMemory.capacity());
            final int localLen = SockaddrIn.setAddress(localAddrMemory, recipient);
            final ByteBuffer peerAddrMemory = senderSockaddrMemory.internalNioBuffer(0, senderSockaddrMemory.capacity());
            final int peerLen = SockaddrIn.setAddress(peerAddrMemory, sender);
            return Long.valueOf(Quiche.quiche_conn_new_with_tls(scidAddr, scidLen, ocidAddr, ocidLen, Quiche.memoryAddressWithPosition(localAddrMemory), localLen, Quiche.memoryAddressWithPosition(peerAddrMemory), peerLen, config.nativeAddress(), ssl, true));
        });
        if (connection == null) {
            channel.unsafe().closeForcibly();
            QuicheQuicServerCodec.LOGGER.debug("quiche_accept failed");
            return null;
        }
        channel.attachQuicheConnection(connection);
        this.addChannel(channel);
        ctx.channel().eventLoop().register(channel);
        return channel;
    }
    
    @Override
    protected void connectQuicChannel(final QuicheQuicChannel channel, final SocketAddress remoteAddress, final SocketAddress localAddress, final ByteBuf senderSockaddrMemory, final ByteBuf recipientSockaddrMemory, final Consumer<QuicheQuicChannel> freeTask, final int localConnIdLength, final QuicheConfig config, final ChannelPromise promise) {
        promise.setFailure((Throwable)new UnsupportedOperationException());
    }
    
    static {
        LOGGER = InternalLoggerFactory.getInstance(QuicheQuicServerCodec.class);
    }
}
