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

package io.netty.handler.codec.http3;

import io.netty.util.internal.logging.InternalLoggerFactory;
import java.util.Iterator;
import io.netty.util.AsciiString;
import io.netty.handler.codec.quic.QuicStreamChannel;
import java.util.ArrayList;
import java.util.function.BiConsumer;
import io.netty.buffer.ByteBuf;
import java.util.List;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.internal.logging.InternalLogger;

final class QpackDecoder
{
    private static final InternalLogger logger;
    private static final QpackException DYNAMIC_TABLE_CAPACITY_EXCEEDS_MAX;
    private static final QpackException HEADER_ILLEGAL_INDEX_VALUE;
    private static final QpackException NAME_ILLEGAL_INDEX_VALUE;
    private static final QpackException INVALID_REQUIRED_INSERT_COUNT;
    private static final QpackException MAX_BLOCKED_STREAMS_EXCEEDED;
    private static final QpackException BLOCKED_STREAM_RESUMPTION_FAILED;
    private static final QpackException UNKNOWN_TYPE;
    private final QpackHuffmanDecoder huffmanDecoder;
    private final QpackDecoderDynamicTable dynamicTable;
    private final long maxTableCapacity;
    private final int maxBlockedStreams;
    private final QpackDecoderStateSyncStrategy stateSyncStrategy;
    private final IntObjectHashMap<List<Runnable>> blockedStreams;
    private final long maxEntries;
    private final long fullRange;
    private int blockedStreamsCount;
    private long lastAckInsertCount;
    
    QpackDecoder(final long maxTableCapacity, final int maxBlockedStreams) {
        this(maxTableCapacity, maxBlockedStreams, new QpackDecoderDynamicTable(), QpackDecoderStateSyncStrategy.ackEachInsert());
    }
    
    QpackDecoder(final long maxTableCapacity, final int maxBlockedStreams, final QpackDecoderDynamicTable dynamicTable, final QpackDecoderStateSyncStrategy stateSyncStrategy) {
        this.huffmanDecoder = new QpackHuffmanDecoder();
        this.maxTableCapacity = maxTableCapacity;
        this.maxBlockedStreams = maxBlockedStreams;
        this.stateSyncStrategy = stateSyncStrategy;
        this.blockedStreams = new IntObjectHashMap<List<Runnable>>(Math.min(16, maxBlockedStreams));
        this.dynamicTable = dynamicTable;
        this.maxEntries = QpackUtil.maxEntries(maxTableCapacity);
        try {
            this.fullRange = QpackUtil.toIntOrThrow(2L * this.maxEntries);
        }
        catch (final QpackException e) {
            throw new IllegalArgumentException(e);
        }
    }
    
    public boolean decode(final QpackAttributes qpackAttributes, final long streamId, ByteBuf in, final int length, final BiConsumer<CharSequence, CharSequence> sink, final Runnable whenDecoded) throws QpackException {
        final int initialReaderIdx = in.readerIndex();
        final int requiredInsertCount = this.decodeRequiredInsertCount(qpackAttributes, in);
        if (this.shouldWaitForDynamicTableUpdates(requiredInsertCount)) {
            ++this.blockedStreamsCount;
            this.blockedStreams.computeIfAbsent(Integer.valueOf(requiredInsertCount), __ -> new ArrayList(2)).add(whenDecoded);
            in.readerIndex(initialReaderIdx);
            return false;
        }
        in = in.readSlice(length - (in.readerIndex() - initialReaderIdx));
        final int base = this.decodeBase(in, requiredInsertCount);
        while (in.isReadable()) {
            final byte b = in.getByte(in.readerIndex());
            if (isIndexed(b)) {
                this.decodeIndexed(in, sink, base);
            }
            else if (isIndexedWithPostBase(b)) {
                this.decodeIndexedWithPostBase(in, sink, base);
            }
            else if (isLiteralWithNameRef(b)) {
                this.decodeLiteralWithNameRef(in, sink, base);
            }
            else if (isLiteralWithPostBaseNameRef(b)) {
                this.decodeLiteralWithPostBaseNameRef(in, sink, base);
            }
            else {
                if (!isLiteral(b)) {
                    throw QpackDecoder.UNKNOWN_TYPE;
                }
                this.decodeLiteral(in, sink);
            }
        }
        if (requiredInsertCount > 0) {
            assert !qpackAttributes.dynamicTableDisabled();
            assert qpackAttributes.decoderStreamAvailable();
            this.stateSyncStrategy.sectionAcknowledged(requiredInsertCount);
            final ByteBuf sectionAck = qpackAttributes.decoderStream().alloc().buffer(8);
            QpackUtil.encodePrefixedInteger(sectionAck, (byte)(-128), 7, streamId);
            Http3CodecUtils.closeOnFailure(qpackAttributes.decoderStream().writeAndFlush(sectionAck));
        }
        return true;
    }
    
    void setDynamicTableCapacity(final long capacity) throws QpackException {
        if (capacity > this.maxTableCapacity) {
            throw QpackDecoder.DYNAMIC_TABLE_CAPACITY_EXCEEDS_MAX;
        }
        this.dynamicTable.setCapacity(capacity);
    }
    
    void insertWithNameReference(final QuicStreamChannel qpackDecoderStream, final boolean staticTableRef, final int nameIdx, final CharSequence value) throws QpackException {
        QpackHeaderField entryForName;
        if (staticTableRef) {
            entryForName = QpackStaticTable.getField(nameIdx);
        }
        else {
            entryForName = this.dynamicTable.getEntryRelativeEncoderInstructions(nameIdx);
        }
        this.dynamicTable.add(new QpackHeaderField(entryForName.name, value));
        this.sendInsertCountIncrementIfRequired(qpackDecoderStream);
    }
    
    void insertLiteral(final QuicStreamChannel qpackDecoderStream, final CharSequence name, final CharSequence value) throws QpackException {
        this.dynamicTable.add(new QpackHeaderField(name, value));
        this.sendInsertCountIncrementIfRequired(qpackDecoderStream);
    }
    
    void duplicate(final QuicStreamChannel qpackDecoderStream, final int index) throws QpackException {
        this.dynamicTable.add(this.dynamicTable.getEntryRelativeEncoderInstructions(index));
        this.sendInsertCountIncrementIfRequired(qpackDecoderStream);
    }
    
    void streamAbandoned(final QuicStreamChannel qpackDecoderStream, final long streamId) {
        if (this.maxTableCapacity == 0L) {
            return;
        }
        final ByteBuf cancel = qpackDecoderStream.alloc().buffer(8);
        QpackUtil.encodePrefixedInteger(cancel, (byte)64, 6, streamId);
        Http3CodecUtils.closeOnFailure(qpackDecoderStream.writeAndFlush(cancel));
    }
    
    private static boolean isIndexed(final byte b) {
        return (b & 0x80) == 0x80;
    }
    
    private static boolean isLiteralWithNameRef(final byte b) {
        return (b & 0xC0) == 0x40;
    }
    
    private static boolean isLiteral(final byte b) {
        return (b & 0xE0) == 0x20;
    }
    
    private static boolean isIndexedWithPostBase(final byte b) {
        return (b & 0xF0) == 0x10;
    }
    
    private static boolean isLiteralWithPostBaseNameRef(final byte b) {
        return (b & 0xF0) == 0x0;
    }
    
    private void decodeIndexed(final ByteBuf in, final BiConsumer<CharSequence, CharSequence> sink, final int base) throws QpackException {
        QpackHeaderField field;
        if (QpackUtil.firstByteEquals(in, (byte)(-64))) {
            final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 6);
            assert idx >= 0;
            if (idx >= QpackStaticTable.length) {
                throw QpackDecoder.HEADER_ILLEGAL_INDEX_VALUE;
            }
            field = QpackStaticTable.getField(idx);
        }
        else {
            final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 6);
            assert idx >= 0;
            field = this.dynamicTable.getEntryRelativeEncodedField(base - idx - 1);
        }
        sink.accept(field.name, field.value);
    }
    
    private void decodeIndexedWithPostBase(final ByteBuf in, final BiConsumer<CharSequence, CharSequence> sink, final int base) throws QpackException {
        final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 4);
        assert idx >= 0;
        final QpackHeaderField field = this.dynamicTable.getEntryRelativeEncodedField(base + idx);
        sink.accept(field.name, field.value);
    }
    
    private void decodeLiteralWithNameRef(final ByteBuf in, final BiConsumer<CharSequence, CharSequence> sink, final int base) throws QpackException {
        CharSequence name;
        if (QpackUtil.firstByteEquals(in, (byte)16)) {
            final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 4);
            assert idx >= 0;
            if (idx >= QpackStaticTable.length) {
                throw QpackDecoder.NAME_ILLEGAL_INDEX_VALUE;
            }
            name = QpackStaticTable.getField(idx).name;
        }
        else {
            final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 4);
            assert idx >= 0;
            name = this.dynamicTable.getEntryRelativeEncodedField(base - idx - 1).name;
        }
        final CharSequence value = this.decodeHuffmanEncodedLiteral(in, 7);
        sink.accept(name, value);
    }
    
    private void decodeLiteralWithPostBaseNameRef(final ByteBuf in, final BiConsumer<CharSequence, CharSequence> sink, final int base) throws QpackException {
        final int idx = QpackUtil.decodePrefixedIntegerAsInt(in, 3);
        assert idx >= 0;
        final CharSequence name = this.dynamicTable.getEntryRelativeEncodedField(base + idx).name;
        final CharSequence value = this.decodeHuffmanEncodedLiteral(in, 7);
        sink.accept(name, value);
    }
    
    private void decodeLiteral(final ByteBuf in, final BiConsumer<CharSequence, CharSequence> sink) throws QpackException {
        final CharSequence name = this.decodeHuffmanEncodedLiteral(in, 3);
        final CharSequence value = this.decodeHuffmanEncodedLiteral(in, 7);
        sink.accept(name, value);
    }
    
    private CharSequence decodeHuffmanEncodedLiteral(final ByteBuf in, final int prefix) throws QpackException {
        assert prefix < 8;
        final boolean huffmanEncoded = QpackUtil.firstByteEquals(in, (byte)(1 << prefix));
        final int length = QpackUtil.decodePrefixedIntegerAsInt(in, prefix);
        assert length >= 0;
        if (huffmanEncoded) {
            return this.huffmanDecoder.decode(in, length);
        }
        final byte[] buf = new byte[length];
        in.readBytes(buf);
        return new AsciiString(buf, false);
    }
    
    int decodeRequiredInsertCount(final QpackAttributes qpackAttributes, final ByteBuf buf) throws QpackException {
        final long encodedInsertCount = QpackUtil.decodePrefixedInteger(buf, 8);
        assert encodedInsertCount >= 0L;
        if (encodedInsertCount == 0L) {
            return 0;
        }
        if (qpackAttributes.dynamicTableDisabled() || encodedInsertCount > this.fullRange) {
            throw QpackDecoder.INVALID_REQUIRED_INSERT_COUNT;
        }
        final long maxValue = this.dynamicTable.insertCount() + this.maxEntries;
        final long maxWrapped = Math.floorDiv(maxValue, this.fullRange) * this.fullRange;
        long requiredInsertCount = maxWrapped + encodedInsertCount - 1L;
        if (requiredInsertCount > maxValue) {
            if (requiredInsertCount <= this.fullRange) {
                throw QpackDecoder.INVALID_REQUIRED_INSERT_COUNT;
            }
            requiredInsertCount -= this.fullRange;
        }
        if (requiredInsertCount == 0L) {
            throw QpackDecoder.INVALID_REQUIRED_INSERT_COUNT;
        }
        return QpackUtil.toIntOrThrow(requiredInsertCount);
    }
    
    int decodeBase(final ByteBuf buf, final int requiredInsertCount) throws QpackException {
        final boolean s = (buf.getByte(buf.readerIndex()) & 0x80) == 0x80;
        final int deltaBase = QpackUtil.decodePrefixedIntegerAsInt(buf, 7);
        assert deltaBase >= 0;
        return s ? (requiredInsertCount - deltaBase - 1) : (requiredInsertCount + deltaBase);
    }
    
    private boolean shouldWaitForDynamicTableUpdates(final int requiredInsertCount) throws QpackException {
        if (requiredInsertCount <= this.dynamicTable.insertCount()) {
            return false;
        }
        if (this.blockedStreamsCount == this.maxBlockedStreams - 1) {
            throw QpackDecoder.MAX_BLOCKED_STREAMS_EXCEEDED;
        }
        return true;
    }
    
    private void sendInsertCountIncrementIfRequired(final QuicStreamChannel qpackDecoderStream) throws QpackException {
        final int insertCount = this.dynamicTable.insertCount();
        final List<Runnable> runnables = this.blockedStreams.get(insertCount);
        if (runnables != null) {
            boolean failed = false;
            for (final Runnable runnable : runnables) {
                try {
                    runnable.run();
                }
                catch (final Exception e) {
                    failed = true;
                    QpackDecoder.logger.error("Failed to resume a blocked stream {}.", runnable, e);
                }
            }
            if (failed) {
                throw QpackDecoder.BLOCKED_STREAM_RESUMPTION_FAILED;
            }
        }
        if (this.stateSyncStrategy.entryAdded(insertCount)) {
            final ByteBuf incr = qpackDecoderStream.alloc().buffer(8);
            QpackUtil.encodePrefixedInteger(incr, (byte)0, 6, insertCount - this.lastAckInsertCount);
            this.lastAckInsertCount = insertCount;
            Http3CodecUtils.closeOnFailure(qpackDecoderStream.writeAndFlush(incr));
        }
    }
    
    static {
        logger = InternalLoggerFactory.getInstance(QpackDecoder.class);
        DYNAMIC_TABLE_CAPACITY_EXCEEDS_MAX = QpackException.newStatic(QpackDecoder.class, "setDynamicTableCapacity(...)", "QPACK - decoder dynamic table capacity exceeds max capacity.");
        HEADER_ILLEGAL_INDEX_VALUE = QpackException.newStatic(QpackDecoder.class, "decodeIndexed(...)", "QPACK - illegal index value");
        NAME_ILLEGAL_INDEX_VALUE = QpackException.newStatic(QpackDecoder.class, "decodeLiteralWithNameRef(...)", "QPACK - illegal name index value");
        INVALID_REQUIRED_INSERT_COUNT = QpackException.newStatic(QpackDecoder.class, "decodeRequiredInsertCount(...)", "QPACK - invalid required insert count");
        MAX_BLOCKED_STREAMS_EXCEEDED = QpackException.newStatic(QpackDecoder.class, "shouldWaitForDynamicTableUpdates(...)", "QPACK - exceeded max blocked streams");
        BLOCKED_STREAM_RESUMPTION_FAILED = QpackException.newStatic(QpackDecoder.class, "sendInsertCountIncrementIfRequired(...)", "QPACK - failed to resume a blocked stream");
        UNKNOWN_TYPE = QpackException.newStatic(QpackDecoder.class, "decode(...)", "QPACK - unknown type");
    }
}
