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

package io.netty.handler.codec.http3;

import io.netty.util.internal.ThrowableUtil;
import java.nio.channels.ClosedChannelException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.handler.codec.quic.QuicStreamChannel;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.handler.codec.quic.QuicStreamType;
import io.netty.channel.Channel;
import io.netty.handler.codec.quic.QuicChannel;
import io.netty.util.ReferenceCountUtil;
import io.netty.channel.ChannelHandlerContext;
import org.jetbrains.annotations.Nullable;
import io.netty.channel.ChannelHandler;

final class Http3ControlStreamInboundHandler extends Http3FrameTypeInboundValidationHandler<Http3ControlStreamFrame>
{
    final boolean server;
    private final ChannelHandler controlFrameHandler;
    private final QpackEncoder qpackEncoder;
    private final Http3ControlStreamOutboundHandler remoteControlStreamHandler;
    private boolean firstFrameRead;
    private Long receivedGoawayId;
    private Long receivedMaxPushId;
    
    Http3ControlStreamInboundHandler(final boolean server, @Nullable final ChannelHandler controlFrameHandler, final QpackEncoder qpackEncoder, final Http3ControlStreamOutboundHandler remoteControlStreamHandler) {
        super(Http3ControlStreamFrame.class);
        this.server = server;
        this.controlFrameHandler = controlFrameHandler;
        this.qpackEncoder = qpackEncoder;
        this.remoteControlStreamHandler = remoteControlStreamHandler;
    }
    
    boolean isServer() {
        return this.server;
    }
    
    boolean isGoAwayReceived() {
        return this.receivedGoawayId != null;
    }
    
    long maxPushIdReceived() {
        return (this.receivedMaxPushId == null) ? -1L : this.receivedMaxPushId;
    }
    
    private boolean forwardControlFrames() {
        return this.controlFrameHandler != null;
    }
    
    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        if (this.controlFrameHandler != null) {
            ctx.pipeline().addLast(this.controlFrameHandler);
        }
    }
    
    @Override
    void readFrameDiscarded(final ChannelHandlerContext ctx, final Object discardedFrame) {
        if (!this.firstFrameRead && !(discardedFrame instanceof Http3SettingsFrame)) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_MISSING_SETTINGS, "Missing settings frame.", this.forwardControlFrames());
        }
    }
    
    @Override
    void channelRead(final ChannelHandlerContext ctx, final Http3ControlStreamFrame frame) throws QpackException {
        final boolean isSettingsFrame = frame instanceof Http3SettingsFrame;
        if (!this.firstFrameRead && !isSettingsFrame) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_MISSING_SETTINGS, "Missing settings frame.", this.forwardControlFrames());
            ReferenceCountUtil.release(frame);
            return;
        }
        if (this.firstFrameRead && isSettingsFrame) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_FRAME_UNEXPECTED, "Second settings frame received.", this.forwardControlFrames());
            ReferenceCountUtil.release(frame);
            return;
        }
        this.firstFrameRead = true;
        boolean valid;
        if (isSettingsFrame) {
            valid = this.handleHttp3SettingsFrame(ctx, (Http3SettingsFrame)frame);
        }
        else if (frame instanceof Http3GoAwayFrame) {
            valid = this.handleHttp3GoAwayFrame(ctx, (Http3GoAwayFrame)frame);
        }
        else if (frame instanceof Http3MaxPushIdFrame) {
            valid = this.handleHttp3MaxPushIdFrame(ctx, (Http3MaxPushIdFrame)frame);
        }
        else if (frame instanceof Http3CancelPushFrame) {
            valid = this.handleHttp3CancelPushFrame(ctx, (Http3CancelPushFrame)frame);
        }
        else {
            assert frame instanceof Http3UnknownFrame;
            valid = true;
        }
        if (!valid || this.controlFrameHandler == null) {
            ReferenceCountUtil.release(frame);
            return;
        }
        ctx.fireChannelRead((Object)frame);
    }
    
    private boolean handleHttp3SettingsFrame(final ChannelHandlerContext ctx, final Http3SettingsFrame settingsFrame) throws QpackException {
        final QuicChannel quicChannel = (QuicChannel)ctx.channel().parent();
        final QpackAttributes qpackAttributes = Http3.getQpackAttributes(quicChannel);
        assert qpackAttributes != null;
        final GenericFutureListener<Future<? super QuicStreamChannel>> closeOnFailure = future -> {
            if (!future.isSuccess()) {
                Http3CodecUtils.criticalStreamClosed(ctx);
            }
            return;
        };
        if (qpackAttributes.dynamicTableDisabled()) {
            this.qpackEncoder.configureDynamicTable(qpackAttributes, 0L, 0);
            return true;
        }
        quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL, new QPackEncoderStreamInitializer(this.qpackEncoder, qpackAttributes, settingsFrame.getOrDefault(Http3SettingsFrame.HTTP3_SETTINGS_QPACK_MAX_TABLE_CAPACITY, 0L), settingsFrame.getOrDefault(Http3SettingsFrame.HTTP3_SETTINGS_QPACK_BLOCKED_STREAMS, 0L))).addListener(closeOnFailure);
        quicChannel.createStream(QuicStreamType.UNIDIRECTIONAL, new QPackDecoderStreamInitializer(qpackAttributes)).addListener(closeOnFailure);
        return true;
    }
    
    private boolean handleHttp3GoAwayFrame(final ChannelHandlerContext ctx, final Http3GoAwayFrame goAwayFrame) {
        final long id = goAwayFrame.id();
        if (!this.server && id % 4L != 0L) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_FRAME_UNEXPECTED, "GOAWAY received with ID of non-request stream.", this.forwardControlFrames());
            return false;
        }
        if (this.receivedGoawayId != null && id > this.receivedGoawayId) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, "GOAWAY received with ID larger than previously received.", this.forwardControlFrames());
            return false;
        }
        this.receivedGoawayId = id;
        return true;
    }
    
    private boolean handleHttp3MaxPushIdFrame(final ChannelHandlerContext ctx, final Http3MaxPushIdFrame frame) {
        final long id = frame.id();
        if (!this.server) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_FRAME_UNEXPECTED, "MAX_PUSH_ID received by client.", this.forwardControlFrames());
            return false;
        }
        if (this.receivedMaxPushId != null && id < this.receivedMaxPushId) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, "MAX_PUSH_ID reduced limit.", this.forwardControlFrames());
            return false;
        }
        this.receivedMaxPushId = id;
        return true;
    }
    
    private boolean handleHttp3CancelPushFrame(final ChannelHandlerContext ctx, final Http3CancelPushFrame cancelPushFrame) {
        final Long maxPushId = this.server ? this.receivedMaxPushId : this.remoteControlStreamHandler.sentMaxPushId();
        if (maxPushId == null || maxPushId < cancelPushFrame.id()) {
            Http3CodecUtils.connectionError(ctx, Http3ErrorCode.H3_ID_ERROR, "CANCEL_PUSH received with an ID greater than MAX_PUSH_ID.", this.forwardControlFrames());
            return false;
        }
        return true;
    }
    
    @Override
    public void channelReadComplete(final ChannelHandlerContext ctx) {
        ctx.fireChannelReadComplete();
        Http3CodecUtils.readIfNoAutoRead(ctx);
    }
    
    @Override
    public boolean isSharable() {
        return false;
    }
    
    @Override
    public void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) {
        if (evt instanceof ChannelInputShutdownEvent) {
            Http3CodecUtils.criticalStreamClosed(ctx);
        }
        ctx.fireUserEventTriggered(evt);
    }
    
    private abstract static class AbstractQPackStreamInitializer extends ChannelInboundHandlerAdapter
    {
        private final int streamType;
        protected final QpackAttributes attributes;
        
        AbstractQPackStreamInitializer(final int streamType, final QpackAttributes attributes) {
            this.streamType = streamType;
            this.attributes = attributes;
        }
        
        @Override
        public final void channelActive(final ChannelHandlerContext ctx) {
            final ByteBuf buffer = ctx.alloc().buffer(8);
            Http3CodecUtils.writeVariableLengthInteger(buffer, this.streamType);
            Http3CodecUtils.closeOnFailure(ctx.writeAndFlush(buffer));
            this.streamAvailable(ctx);
            ctx.fireChannelActive();
        }
        
        @Override
        public final void userEventTriggered(final ChannelHandlerContext ctx, final Object evt) {
            this.streamClosed(ctx);
            if (evt instanceof ChannelInputShutdownEvent) {
                Http3CodecUtils.criticalStreamClosed(ctx);
            }
            ctx.fireUserEventTriggered(evt);
        }
        
        @Override
        public void channelInactive(final ChannelHandlerContext ctx) {
            this.streamClosed(ctx);
            Http3CodecUtils.criticalStreamClosed(ctx);
            ctx.fireChannelInactive();
        }
        
        protected abstract void streamAvailable(final ChannelHandlerContext p0);
        
        protected abstract void streamClosed(final ChannelHandlerContext p0);
    }
    
    private static final class QPackEncoderStreamInitializer extends AbstractQPackStreamInitializer
    {
        private static final ClosedChannelException ENCODER_STREAM_INACTIVE;
        private final QpackEncoder encoder;
        private final long maxTableCapacity;
        private final long blockedStreams;
        
        QPackEncoderStreamInitializer(final QpackEncoder encoder, final QpackAttributes attributes, final long maxTableCapacity, final long blockedStreams) {
            super(2, attributes);
            this.encoder = encoder;
            this.maxTableCapacity = maxTableCapacity;
            this.blockedStreams = blockedStreams;
        }
        
        @Override
        protected void streamAvailable(final ChannelHandlerContext ctx) {
            final QuicStreamChannel stream = (QuicStreamChannel)ctx.channel();
            this.attributes.encoderStream(stream);
            try {
                this.encoder.configureDynamicTable(this.attributes, this.maxTableCapacity, QpackUtil.toIntOrThrow(this.blockedStreams));
            }
            catch (final QpackException e) {
                Http3CodecUtils.connectionError(ctx, new Http3Exception(Http3ErrorCode.QPACK_ENCODER_STREAM_ERROR, "Dynamic table configuration failed.", e), true);
            }
        }
        
        @Override
        protected void streamClosed(final ChannelHandlerContext ctx) {
            this.attributes.encoderStreamInactive(QPackEncoderStreamInitializer.ENCODER_STREAM_INACTIVE);
        }
        
        static {
            ENCODER_STREAM_INACTIVE = ThrowableUtil.unknownStackTrace(new ClosedChannelException(), ClosedChannelException.class, "streamClosed()");
        }
    }
    
    private static final class QPackDecoderStreamInitializer extends AbstractQPackStreamInitializer
    {
        private static final ClosedChannelException DECODER_STREAM_INACTIVE;
        
        private QPackDecoderStreamInitializer(final QpackAttributes attributes) {
            super(3, attributes);
        }
        
        @Override
        protected void streamAvailable(final ChannelHandlerContext ctx) {
            this.attributes.decoderStream((QuicStreamChannel)ctx.channel());
        }
        
        @Override
        protected void streamClosed(final ChannelHandlerContext ctx) {
            this.attributes.decoderStreamInactive(QPackDecoderStreamInitializer.DECODER_STREAM_INACTIVE);
        }
        
        static {
            DECODER_STREAM_INACTIVE = ThrowableUtil.unknownStackTrace(new ClosedChannelException(), ClosedChannelException.class, "streamClosed()");
        }
    }
}
