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

package io.netty.handler.codec.quic;

import io.netty.buffer.Unpooled;
import io.netty.channel.socket.ChannelInputShutdownReadComplete;
import io.netty.channel.socket.ChannelInputShutdownEvent;
import io.netty.channel.ChannelOutboundBuffer;
import io.netty.util.internal.StringUtil;
import io.netty.buffer.ByteBuf;
import io.netty.util.ReferenceCountUtil;
import java.util.concurrent.RejectedExecutionException;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.PromiseNotifier;
import io.netty.util.concurrent.Promise;
import org.jetbrains.annotations.Nullable;
import java.nio.channels.ClosedChannelException;
import io.netty.channel.VoidChannelPromise;
import io.netty.channel.RecvByteBufAllocator;
import io.netty.util.internal.logging.InternalLoggerFactory;
import io.netty.channel.ChannelOutboundInvoker;
import io.netty.channel.ChannelConfig;
import java.net.SocketAddress;
import io.netty.channel.socket.ChannelOutputShutdownException;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.EventLoop;
import io.netty.channel.ChannelFuture;
import io.netty.channel.Channel;
import io.netty.channel.DefaultChannelPipeline;
import io.netty.channel.DefaultChannelId;
import io.netty.channel.PendingWriteQueue;
import io.netty.channel.ChannelPromise;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelId;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.channel.ChannelMetadata;
import io.netty.util.DefaultAttributeMap;

final class QuicheQuicStreamChannel extends DefaultAttributeMap implements QuicStreamChannel
{
    private static final ChannelMetadata METADATA;
    private static final InternalLogger LOGGER;
    private final QuicheQuicChannel parent;
    private final ChannelId id;
    private final ChannelPipeline pipeline;
    private final QuicStreamChannelUnsafe unsafe;
    private final ChannelPromise closePromise;
    private final PendingWriteQueue queue;
    private final QuicStreamChannelConfig config;
    private final QuicStreamAddress address;
    private boolean readable;
    private boolean readPending;
    private boolean inRecv;
    private boolean inWriteQueued;
    private boolean finReceived;
    private boolean finSent;
    private volatile boolean registered;
    private volatile boolean writable;
    private volatile boolean active;
    private volatile boolean inputShutdown;
    private volatile boolean outputShutdown;
    private volatile QuicStreamPriority priority;
    private volatile long capacity;
    
    QuicheQuicStreamChannel(final QuicheQuicChannel parent, final long streamId) {
        this.writable = true;
        this.active = true;
        this.parent = parent;
        this.id = DefaultChannelId.newInstance();
        this.unsafe = new QuicStreamChannelUnsafe();
        this.pipeline = new DefaultChannelPipeline(this) {};
        this.config = new QuicheQuicStreamChannelConfig(this);
        this.address = new QuicStreamAddress(streamId);
        this.closePromise = this.newPromise();
        this.queue = new PendingWriteQueue(this);
        if (parent.streamType(streamId) == QuicStreamType.UNIDIRECTIONAL && parent.isStreamLocalCreated(streamId)) {
            this.inputShutdown = true;
        }
    }
    
    @Override
    public QuicStreamAddress localAddress() {
        return this.address;
    }
    
    @Override
    public QuicStreamAddress remoteAddress() {
        return this.address;
    }
    
    @Override
    public boolean isLocalCreated() {
        return this.parent().isStreamLocalCreated(this.streamId());
    }
    
    @Override
    public QuicStreamType type() {
        return this.parent().streamType(this.streamId());
    }
    
    @Override
    public long streamId() {
        return this.address.streamId();
    }
    
    @Override
    public QuicStreamPriority priority() {
        return this.priority;
    }
    
    @Override
    public ChannelFuture updatePriority(final QuicStreamPriority priority, final ChannelPromise promise) {
        if (this.eventLoop().inEventLoop()) {
            this.updatePriority0(priority, promise);
        }
        else {
            this.eventLoop().execute(() -> this.updatePriority0(priority, promise));
        }
        return promise;
    }
    
    private void updatePriority0(final QuicStreamPriority priority, final ChannelPromise promise) {
        assert this.eventLoop().inEventLoop();
        if (!promise.setUncancellable()) {
            return;
        }
        try {
            this.parent().streamPriority(this.streamId(), (byte)priority.urgency(), priority.isIncremental());
        }
        catch (final Throwable cause) {
            promise.setFailure(cause);
            return;
        }
        this.priority = priority;
        promise.setSuccess();
    }
    
    @Override
    public boolean isInputShutdown() {
        return this.inputShutdown;
    }
    
    @Override
    public ChannelFuture shutdownOutput(final ChannelPromise promise) {
        if (this.eventLoop().inEventLoop()) {
            this.shutdownOutput0(promise);
        }
        else {
            this.eventLoop().execute(() -> this.shutdownOutput0(promise));
        }
        return promise;
    }
    
    private void shutdownOutput0(final ChannelPromise promise) {
        assert this.eventLoop().inEventLoop();
        if (!promise.setUncancellable()) {
            return;
        }
        this.outputShutdown = true;
        this.unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, promise);
        this.unsafe.flush();
    }
    
    @Override
    public ChannelFuture shutdownInput(final int error, final ChannelPromise promise) {
        if (this.eventLoop().inEventLoop()) {
            this.shutdownInput0(error, promise);
        }
        else {
            this.eventLoop().execute(() -> this.shutdownInput0(error, promise));
        }
        return promise;
    }
    
    @Override
    public ChannelFuture shutdownOutput(final int error, final ChannelPromise promise) {
        if (this.eventLoop().inEventLoop()) {
            this.shutdownOutput0(error, promise);
        }
        else {
            this.eventLoop().execute(() -> this.shutdownOutput0(error, promise));
        }
        return promise;
    }
    
    @Override
    public QuicheQuicChannel parent() {
        return this.parent;
    }
    
    private void shutdownInput0(final int err, final ChannelPromise channelPromise) {
        assert this.eventLoop().inEventLoop();
        if (!channelPromise.setUncancellable()) {
            return;
        }
        this.inputShutdown = true;
        this.parent().streamShutdown(this.streamId(), true, false, err, channelPromise);
        this.closeIfDone();
    }
    
    @Override
    public boolean isOutputShutdown() {
        return this.outputShutdown;
    }
    
    private void shutdownOutput0(final int error, final ChannelPromise channelPromise) {
        assert this.eventLoop().inEventLoop();
        if (!channelPromise.setUncancellable()) {
            return;
        }
        this.parent().streamShutdown(this.streamId(), false, true, error, channelPromise);
        this.outputShutdown = true;
        this.closeIfDone();
    }
    
    @Override
    public boolean isShutdown() {
        return this.outputShutdown && this.inputShutdown;
    }
    
    @Override
    public ChannelFuture shutdown(final ChannelPromise channelPromise) {
        if (this.eventLoop().inEventLoop()) {
            this.shutdown0(channelPromise);
        }
        else {
            this.eventLoop().execute(() -> this.shutdown0(channelPromise));
        }
        return channelPromise;
    }
    
    private void shutdown0(final ChannelPromise promise) {
        assert this.eventLoop().inEventLoop();
        if (!promise.setUncancellable()) {
            return;
        }
        this.inputShutdown = true;
        this.outputShutdown = true;
        this.unsafe.writeWithoutCheckChannelState(QuicStreamFrame.EMPTY_FIN, this.unsafe.voidPromise());
        this.unsafe.flush();
        this.parent().streamShutdown(this.streamId(), true, false, 0, promise);
        this.closeIfDone();
    }
    
    @Override
    public ChannelFuture shutdown(final int error, final ChannelPromise promise) {
        if (this.eventLoop().inEventLoop()) {
            this.shutdown0(error, promise);
        }
        else {
            this.eventLoop().execute(() -> this.shutdown0(error, promise));
        }
        return promise;
    }
    
    private void shutdown0(final int error, final ChannelPromise channelPromise) {
        assert this.eventLoop().inEventLoop();
        if (!channelPromise.setUncancellable()) {
            return;
        }
        this.inputShutdown = true;
        this.outputShutdown = true;
        this.parent().streamShutdown(this.streamId(), true, true, error, channelPromise);
        this.closeIfDone();
    }
    
    private void sendFinIfNeeded() throws Exception {
        if (!this.finSent) {
            this.finSent = true;
            this.parent().streamSendFin(this.streamId());
        }
    }
    
    private void closeIfDone() {
        if (this.finSent && (this.finReceived || (this.type() == QuicStreamType.UNIDIRECTIONAL && this.isLocalCreated()))) {
            this.unsafe().close(this.unsafe().voidPromise());
        }
    }
    
    private void removeStreamFromParent() {
        if (!this.active && this.finReceived) {
            this.parent().streamClosed(this.streamId());
            this.inputShutdown = true;
            this.outputShutdown = true;
        }
    }
    
    @Override
    public QuicStreamChannel flush() {
        this.pipeline.flush();
        return this;
    }
    
    @Override
    public QuicStreamChannel read() {
        this.pipeline.read();
        return this;
    }
    
    @Override
    public QuicStreamChannelConfig config() {
        return this.config;
    }
    
    @Override
    public boolean isOpen() {
        return this.active;
    }
    
    @Override
    public boolean isActive() {
        return this.isOpen();
    }
    
    @Override
    public ChannelMetadata metadata() {
        return QuicheQuicStreamChannel.METADATA;
    }
    
    @Override
    public ChannelId id() {
        return this.id;
    }
    
    @Override
    public EventLoop eventLoop() {
        return this.parent.eventLoop();
    }
    
    @Override
    public boolean isRegistered() {
        return this.registered;
    }
    
    @Override
    public ChannelFuture closeFuture() {
        return this.closePromise;
    }
    
    @Override
    public boolean isWritable() {
        return this.writable;
    }
    
    @Override
    public long bytesBeforeUnwritable() {
        return Math.max(this.capacity, 0L);
    }
    
    @Override
    public long bytesBeforeWritable() {
        if (this.writable) {
            return 0L;
        }
        return 8L;
    }
    
    @Override
    public QuicStreamChannelUnsafe unsafe() {
        return this.unsafe;
    }
    
    @Override
    public ChannelPipeline pipeline() {
        return this.pipeline;
    }
    
    @Override
    public ByteBufAllocator alloc() {
        return this.config.getAllocator();
    }
    
    @Override
    public int compareTo(final Channel o) {
        return this.id.compareTo(o.id());
    }
    
    @Override
    public int hashCode() {
        return this.id.hashCode();
    }
    
    @Override
    public boolean equals(final Object o) {
        return this == o;
    }
    
    @Override
    public String toString() {
        return "[id: 0x" + this.id.asShortText() + ", " + this.address + "]";
    }
    
    boolean writable(final long capacity) {
        assert this.eventLoop().inEventLoop();
        if (capacity < 0L) {
            if (capacity != Quiche.QUICHE_ERR_DONE) {
                if (!this.queue.isEmpty()) {
                    if (capacity == Quiche.QUICHE_ERR_STREAM_STOPPED) {
                        this.queue.removeAndFailAll(new ChannelOutputShutdownException("STOP_SENDING frame received"));
                        return false;
                    }
                    this.queue.removeAndFailAll(Quiche.convertToException((int)capacity));
                }
                else if (capacity == Quiche.QUICHE_ERR_STREAM_STOPPED) {
                    return false;
                }
                this.finSent = true;
                this.unsafe().close(this.unsafe().voidPromise());
            }
            return false;
        }
        this.capacity = capacity;
        final boolean mayNeedWrite = this.unsafe().writeQueued();
        this.updateWritabilityIfNeeded(this.capacity > 0L);
        return mayNeedWrite;
    }
    
    private void updateWritabilityIfNeeded(final boolean newWritable) {
        if (this.writable != newWritable) {
            this.writable = newWritable;
            this.pipeline.fireChannelWritabilityChanged();
        }
    }
    
    void readable() {
        assert this.eventLoop().inEventLoop();
        this.readable = true;
        if (this.readPending) {
            this.unsafe().recv();
        }
    }
    
    static {
        METADATA = new ChannelMetadata(false, 16);
        LOGGER = InternalLoggerFactory.getInstance(QuicheQuicStreamChannel.class);
    }
    
    final class QuicStreamChannelUnsafe implements Channel.Unsafe
    {
        private RecvByteBufAllocator.Handle recvHandle;
        private final ChannelPromise voidPromise;
        
        QuicStreamChannelUnsafe() {
            this.voidPromise = new VoidChannelPromise(QuicheQuicStreamChannel.this, false);
        }
        
        @Override
        public void connect(final SocketAddress remote, final SocketAddress local, final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            promise.setFailure((Throwable)new UnsupportedOperationException());
        }
        
        @Override
        public RecvByteBufAllocator.Handle recvBufAllocHandle() {
            if (this.recvHandle == null) {
                this.recvHandle = QuicheQuicStreamChannel.this.config.getRecvByteBufAllocator().newHandle();
            }
            return this.recvHandle;
        }
        
        @Override
        public SocketAddress localAddress() {
            return QuicheQuicStreamChannel.this.address;
        }
        
        @Override
        public SocketAddress remoteAddress() {
            return QuicheQuicStreamChannel.this.address;
        }
        
        @Override
        public void register(final EventLoop eventLoop, final ChannelPromise promise) {
            assert eventLoop.inEventLoop();
            if (!promise.setUncancellable()) {
                return;
            }
            if (QuicheQuicStreamChannel.this.registered) {
                promise.setFailure((Throwable)new IllegalStateException());
                return;
            }
            if (eventLoop != QuicheQuicStreamChannel.this.parent.eventLoop()) {
                promise.setFailure((Throwable)new IllegalArgumentException());
                return;
            }
            QuicheQuicStreamChannel.this.registered = true;
            promise.setSuccess();
            QuicheQuicStreamChannel.this.pipeline.fireChannelRegistered();
            QuicheQuicStreamChannel.this.pipeline.fireChannelActive();
        }
        
        @Override
        public void bind(final SocketAddress localAddress, final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            if (!promise.setUncancellable()) {
                return;
            }
            promise.setFailure((Throwable)new UnsupportedOperationException());
        }
        
        @Override
        public void disconnect(final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            this.close(promise);
        }
        
        @Override
        public void close(final ChannelPromise promise) {
            this.close(null, promise);
        }
        
        void close(@Nullable ClosedChannelException writeFailCause, final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            if (!promise.setUncancellable()) {
                return;
            }
            if (QuicheQuicStreamChannel.this.active && !QuicheQuicStreamChannel.this.closePromise.isDone()) {
                QuicheQuicStreamChannel.this.active = false;
                try {
                    QuicheQuicStreamChannel.this.sendFinIfNeeded();
                }
                catch (final Exception ex) {}
                finally {
                    if (!QuicheQuicStreamChannel.this.queue.isEmpty()) {
                        if (writeFailCause == null) {
                            writeFailCause = new ClosedChannelException();
                        }
                        QuicheQuicStreamChannel.this.queue.removeAndFailAll(writeFailCause);
                    }
                    promise.trySuccess();
                    QuicheQuicStreamChannel.this.closePromise.trySuccess();
                    if (QuicheQuicStreamChannel.this.type() == QuicStreamType.UNIDIRECTIONAL && QuicheQuicStreamChannel.this.isLocalCreated()) {
                        QuicheQuicStreamChannel.this.inputShutdown = true;
                        QuicheQuicStreamChannel.this.outputShutdown = true;
                        QuicheQuicStreamChannel.this.parent().streamClosed(QuicheQuicStreamChannel.this.streamId());
                    }
                    else {
                        QuicheQuicStreamChannel.this.removeStreamFromParent();
                    }
                }
                if (QuicheQuicStreamChannel.this.inWriteQueued) {
                    this.invokeLater(() -> this.deregister(this.voidPromise(), true));
                }
                else {
                    this.deregister(this.voidPromise(), true);
                }
                return;
            }
            if (promise.isVoid()) {
                return;
            }
            QuicheQuicStreamChannel.this.closePromise.addListener((GenericFutureListener<? extends Future<? super Void>>)new PromiseNotifier<Object, Future<? super Void>>((Promise<?>[])new Promise[] { promise }));
        }
        
        private void deregister(final ChannelPromise promise, final boolean fireChannelInactive) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            if (!promise.setUncancellable()) {
                return;
            }
            if (!QuicheQuicStreamChannel.this.registered) {
                promise.trySuccess();
                return;
            }
            this.invokeLater(() -> {
                if (fireChannelInactive) {
                    QuicheQuicStreamChannel.this.pipeline.fireChannelInactive();
                }
                if (QuicheQuicStreamChannel.this.registered) {
                    QuicheQuicStreamChannel.this.registered = false;
                    QuicheQuicStreamChannel.this.pipeline.fireChannelUnregistered();
                }
                promise.setSuccess();
            });
        }
        
        private void invokeLater(final Runnable task) {
            try {
                QuicheQuicStreamChannel.this.eventLoop().execute(task);
            }
            catch (final RejectedExecutionException e) {
                QuicheQuicStreamChannel.LOGGER.warn("Can't invoke task later as EventLoop rejected it", e);
            }
        }
        
        @Override
        public void closeForcibly() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            this.close(QuicheQuicStreamChannel.this.unsafe().voidPromise());
        }
        
        @Override
        public void deregister(final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            this.deregister(promise, false);
        }
        
        @Override
        public void beginRead() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            QuicheQuicStreamChannel.this.readPending = true;
            if (QuicheQuicStreamChannel.this.readable) {
                QuicheQuicStreamChannel.this.unsafe().recv();
                QuicheQuicStreamChannel.this.parent().connectionSendAndFlush();
            }
        }
        
        private void closeIfNeeded(final boolean wasFinSent) {
            if (!wasFinSent && QuicheQuicStreamChannel.this.finSent && (QuicheQuicStreamChannel.this.type() == QuicStreamType.UNIDIRECTIONAL || QuicheQuicStreamChannel.this.finReceived)) {
                this.close(this.voidPromise());
            }
        }
        
        boolean writeQueued() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            final boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
            QuicheQuicStreamChannel.this.inWriteQueued = true;
            try {
                if (QuicheQuicStreamChannel.this.queue.isEmpty()) {
                    return false;
                }
                boolean written = false;
                while (true) {
                    final Object msg = QuicheQuicStreamChannel.this.queue.current();
                    if (msg == null) {
                        break;
                    }
                    try {
                        final int res = this.write0(msg);
                        if (res == 1) {
                            QuicheQuicStreamChannel.this.queue.remove().setSuccess();
                            written = true;
                        }
                        else {
                            if (res == 0 || res == Quiche.QUICHE_ERR_DONE) {
                                break;
                            }
                            if (res == Quiche.QUICHE_ERR_STREAM_STOPPED) {
                                QuicheQuicStreamChannel.this.queue.removeAndFailAll(new ChannelOutputShutdownException("STOP_SENDING frame received"));
                                break;
                            }
                            QuicheQuicStreamChannel.this.queue.remove().setFailure((Throwable)Quiche.convertToException(res));
                        }
                    }
                    catch (final Exception e) {
                        QuicheQuicStreamChannel.this.queue.remove().setFailure((Throwable)e);
                    }
                }
                if (written) {
                    QuicheQuicStreamChannel.this.updateWritabilityIfNeeded(true);
                }
                return written;
            }
            finally {
                this.closeIfNeeded(wasFinSent);
                QuicheQuicStreamChannel.this.inWriteQueued = false;
            }
        }
        
        @Override
        public void write(Object msg, final ChannelPromise promise) {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            if (!promise.setUncancellable()) {
                ReferenceCountUtil.release(msg);
                return;
            }
            if (!QuicheQuicStreamChannel.this.isOpen()) {
                this.queueAndFailAll(msg, promise, new ClosedChannelException());
            }
            else if (QuicheQuicStreamChannel.this.finSent) {
                this.queueAndFailAll(msg, promise, new ChannelOutputShutdownException("Fin was sent already"));
            }
            else if (!QuicheQuicStreamChannel.this.queue.isEmpty()) {
                try {
                    msg = this.filterMsg(msg);
                }
                catch (final UnsupportedOperationException e) {
                    ReferenceCountUtil.release(msg);
                    promise.setFailure((Throwable)e);
                    return;
                }
                ReferenceCountUtil.touch(msg);
                QuicheQuicStreamChannel.this.queue.add(msg, promise);
                this.writeQueued();
            }
            else {
                assert QuicheQuicStreamChannel.this.queue.isEmpty();
                this.writeWithoutCheckChannelState(msg, promise);
            }
        }
        
        private void queueAndFailAll(final Object msg, final ChannelPromise promise, final Throwable cause) {
            ReferenceCountUtil.touch(msg);
            QuicheQuicStreamChannel.this.queue.add(msg, promise);
            QuicheQuicStreamChannel.this.queue.removeAndFailAll(cause);
        }
        
        private Object filterMsg(final Object msg) {
            if (msg instanceof ByteBuf) {
                final ByteBuf buffer = (ByteBuf)msg;
                if (!buffer.isDirect()) {
                    final ByteBuf tmpBuffer = QuicheQuicStreamChannel.this.alloc().directBuffer(buffer.readableBytes());
                    tmpBuffer.writeBytes(buffer, buffer.readerIndex(), buffer.readableBytes());
                    buffer.release();
                    return tmpBuffer;
                }
            }
            else {
                if (!(msg instanceof QuicStreamFrame)) {
                    throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg));
                }
                final QuicStreamFrame frame = (QuicStreamFrame)msg;
                final ByteBuf buffer2 = frame.content();
                if (!buffer2.isDirect()) {
                    final ByteBuf tmpBuffer2 = QuicheQuicStreamChannel.this.alloc().directBuffer(buffer2.readableBytes());
                    tmpBuffer2.writeBytes(buffer2, buffer2.readerIndex(), buffer2.readableBytes());
                    final QuicStreamFrame tmpFrame = frame.replace(tmpBuffer2);
                    frame.release();
                    return tmpFrame;
                }
            }
            return msg;
        }
        
        void writeWithoutCheckChannelState(Object msg, final ChannelPromise promise) {
            try {
                msg = this.filterMsg(msg);
            }
            catch (final UnsupportedOperationException e) {
                ReferenceCountUtil.release(msg);
                promise.setFailure((Throwable)e);
            }
            final boolean wasFinSent = QuicheQuicStreamChannel.this.finSent;
            boolean mayNeedWritabilityUpdate = false;
            try {
                final int res = this.write0(msg);
                if (res > 0) {
                    ReferenceCountUtil.release(msg);
                    promise.setSuccess();
                    mayNeedWritabilityUpdate = (QuicheQuicStreamChannel.this.capacity == 0L);
                }
                else if (res == 0 || res == Quiche.QUICHE_ERR_DONE) {
                    ReferenceCountUtil.touch(msg);
                    QuicheQuicStreamChannel.this.queue.add(msg, promise);
                    mayNeedWritabilityUpdate = true;
                }
                else {
                    if (res == Quiche.QUICHE_ERR_STREAM_STOPPED) {
                        throw new ChannelOutputShutdownException("STOP_SENDING frame received");
                    }
                    throw Quiche.convertToException(res);
                }
            }
            catch (final Exception e2) {
                ReferenceCountUtil.release(msg);
                promise.setFailure((Throwable)e2);
                mayNeedWritabilityUpdate = (QuicheQuicStreamChannel.this.capacity == 0L);
            }
            finally {
                if (mayNeedWritabilityUpdate) {
                    QuicheQuicStreamChannel.this.updateWritabilityIfNeeded(false);
                }
                this.closeIfNeeded(wasFinSent);
            }
        }
        
        private int write0(final Object msg) throws Exception {
            if (QuicheQuicStreamChannel.this.type() == QuicStreamType.UNIDIRECTIONAL && !QuicheQuicStreamChannel.this.isLocalCreated()) {
                throw new UnsupportedOperationException("Writes on non-local created streams that are unidirectional are not supported");
            }
            if (QuicheQuicStreamChannel.this.finSent) {
                throw new ChannelOutputShutdownException("Fin was sent already");
            }
            boolean fin;
            ByteBuf buffer;
            if (msg instanceof ByteBuf) {
                fin = false;
                buffer = (ByteBuf)msg;
            }
            else {
                final QuicStreamFrame frame = (QuicStreamFrame)msg;
                fin = frame.hasFin();
                buffer = frame.content();
            }
            final boolean readable = buffer.isReadable();
            if (!fin && !readable) {
                return 1;
            }
            boolean sendSomething = false;
            try {
                do {
                    final int res = QuicheQuicStreamChannel.this.parent().streamSend(QuicheQuicStreamChannel.this.streamId(), buffer, fin);
                    final long cap = QuicheQuicStreamChannel.this.parent.streamCapacity(QuicheQuicStreamChannel.this.streamId());
                    if (cap >= 0L) {
                        QuicheQuicStreamChannel.this.capacity = cap;
                    }
                    if (res < 0) {
                        return res;
                    }
                    if (readable && res == 0) {
                        return 0;
                    }
                    sendSomething = true;
                    buffer.skipBytes(res);
                } while (buffer.isReadable());
                if (fin) {
                    QuicheQuicStreamChannel.this.finSent = true;
                    QuicheQuicStreamChannel.this.outputShutdown = true;
                }
                return 1;
            }
            finally {
                if (sendSomething) {
                    QuicheQuicStreamChannel.this.parent.connectionSendAndFlush();
                }
            }
        }
        
        @Override
        public void flush() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
        }
        
        @Override
        public ChannelPromise voidPromise() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            return this.voidPromise;
        }
        
        @Nullable
        @Override
        public ChannelOutboundBuffer outboundBuffer() {
            return null;
        }
        
        private void closeOnRead(final ChannelPipeline pipeline, final boolean readFrames) {
            if (readFrames && QuicheQuicStreamChannel.this.finReceived && QuicheQuicStreamChannel.this.finSent) {
                this.close(this.voidPromise());
            }
            else if (QuicheQuicStreamChannel.this.config.isAllowHalfClosure()) {
                if (QuicheQuicStreamChannel.this.finReceived) {
                    pipeline.fireUserEventTriggered((Object)ChannelInputShutdownEvent.INSTANCE);
                    pipeline.fireUserEventTriggered((Object)ChannelInputShutdownReadComplete.INSTANCE);
                    if (QuicheQuicStreamChannel.this.finSent) {
                        this.close(this.voidPromise());
                    }
                }
            }
            else {
                this.close(this.voidPromise());
            }
        }
        
        private void handleReadException(final ChannelPipeline pipeline, @Nullable final ByteBuf byteBuf, final Throwable cause, final RecvByteBufAllocator.Handle allocHandle, final boolean readFrames) {
            if (byteBuf != null) {
                if (byteBuf.isReadable()) {
                    pipeline.fireChannelRead((Object)byteBuf);
                }
                else {
                    byteBuf.release();
                }
            }
            this.readComplete(allocHandle, pipeline);
            pipeline.fireExceptionCaught(cause);
            if (QuicheQuicStreamChannel.this.finReceived) {
                this.closeOnRead(pipeline, readFrames);
            }
        }
        
        void recv() {
            assert QuicheQuicStreamChannel.this.eventLoop().inEventLoop();
            if (QuicheQuicStreamChannel.this.inRecv) {
                return;
            }
            QuicheQuicStreamChannel.this.inRecv = true;
            try {
                final ChannelPipeline pipeline = QuicheQuicStreamChannel.this.pipeline();
                final QuicheQuicStreamChannelConfig config = (QuicheQuicStreamChannelConfig)QuicheQuicStreamChannel.this.config();
                final DirectIoByteBufAllocator allocator = config.allocator;
                final RecvByteBufAllocator.Handle allocHandle = this.recvBufAllocHandle();
                final boolean readFrames = config.isReadFrames();
                while (QuicheQuicStreamChannel.this.active && QuicheQuicStreamChannel.this.readPending && QuicheQuicStreamChannel.this.readable) {
                    allocHandle.reset(config);
                    ByteBuf byteBuf = null;
                    final QuicheQuicChannel parent = QuicheQuicStreamChannel.this.parent();
                    boolean readCompleteNeeded = false;
                    boolean continueReading = true;
                    try {
                        while (!QuicheQuicStreamChannel.this.finReceived && continueReading) {
                            byteBuf = allocHandle.allocate(allocator);
                            allocHandle.attemptedBytesRead(byteBuf.writableBytes());
                            final QuicheQuicChannel.StreamRecvResult result = parent.streamRecv(QuicheQuicStreamChannel.this.streamId(), byteBuf);
                            switch (result) {
                                case DONE: {
                                    QuicheQuicStreamChannel.this.readable = false;
                                    break;
                                }
                                case FIN: {
                                    QuicheQuicStreamChannel.this.readable = false;
                                    QuicheQuicStreamChannel.this.finReceived = true;
                                    QuicheQuicStreamChannel.this.inputShutdown = true;
                                    break;
                                }
                                case OK: {
                                    break;
                                }
                                default: {
                                    throw new Error("Unexpected StreamRecvResult: " + result);
                                }
                            }
                            allocHandle.lastBytesRead(byteBuf.readableBytes());
                            if (allocHandle.lastBytesRead() <= 0) {
                                byteBuf.release();
                                if (!QuicheQuicStreamChannel.this.finReceived || !readFrames) {
                                    byteBuf = null;
                                    break;
                                }
                                byteBuf = Unpooled.EMPTY_BUFFER;
                            }
                            allocHandle.incMessagesRead(1);
                            readCompleteNeeded = true;
                            QuicheQuicStreamChannel.this.readPending = false;
                            if (readFrames) {
                                pipeline.fireChannelRead((Object)new DefaultQuicStreamFrame(byteBuf, QuicheQuicStreamChannel.this.finReceived));
                            }
                            else {
                                pipeline.fireChannelRead((Object)byteBuf);
                            }
                            byteBuf = null;
                            continueReading = allocHandle.continueReading();
                        }
                        if (readCompleteNeeded) {
                            this.readComplete(allocHandle, pipeline);
                        }
                        if (!QuicheQuicStreamChannel.this.finReceived) {
                            continue;
                        }
                        QuicheQuicStreamChannel.this.readable = false;
                        this.closeOnRead(pipeline, readFrames);
                    }
                    catch (final Throwable cause) {
                        QuicheQuicStreamChannel.this.readable = false;
                        this.handleReadException(pipeline, byteBuf, cause, allocHandle, readFrames);
                    }
                }
            }
            finally {
                QuicheQuicStreamChannel.this.inRecv = false;
                QuicheQuicStreamChannel.this.removeStreamFromParent();
            }
        }
        
        private void readComplete(final RecvByteBufAllocator.Handle allocHandle, final ChannelPipeline pipeline) {
            allocHandle.readComplete();
            pipeline.fireChannelReadComplete();
        }
    }
}
