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

package io.netty.handler.codec.quic;

import io.netty.buffer.Unpooled;
import io.netty.buffer.ByteBuf;
import java.net.InetSocketAddress;
import io.netty.util.internal.ObjectUtil;

public final class QuicHeaderParser implements AutoCloseable
{
    private static final int AES_128_GCM_TAG_LENGTH = 16;
    private final int localConnectionIdLength;
    private boolean closed;
    
    public QuicHeaderParser(final int localConnectionIdLength) {
        this.localConnectionIdLength = ObjectUtil.checkPositiveOrZero(localConnectionIdLength, "localConnectionIdLength");
    }
    
    @Override
    public void close() {
        if (!this.closed) {
            this.closed = true;
        }
    }
    
    public void parse(final InetSocketAddress sender, final InetSocketAddress recipient, final ByteBuf packet, final QuicHeaderProcessor callback) throws Exception {
        if (this.closed) {
            throw new IllegalStateException(QuicHeaderParser.class.getSimpleName() + " is already closed");
        }
        int offset = 0;
        final int readable = packet.readableBytes();
        checkReadable(offset, readable, 1);
        final byte first = packet.getByte(offset);
        ++offset;
        long version;
        QuicPacketType type;
        ByteBuf scid;
        ByteBuf token;
        ByteBuf dcid;
        if (hasShortHeader(first)) {
            version = 0L;
            type = QuicPacketType.SHORT;
            scid = Unpooled.EMPTY_BUFFER;
            token = Unpooled.EMPTY_BUFFER;
            dcid = sliceCid(packet, offset, this.localConnectionIdLength);
        }
        else {
            checkReadable(offset, readable, 4);
            version = packet.getUnsignedInt(offset);
            offset += 4;
            type = typeOfLongHeader(first, version);
            final int dcidLen = packet.getUnsignedByte(offset);
            checkCidLength(dcidLen);
            ++offset;
            dcid = sliceCid(packet, offset, dcidLen);
            offset += dcidLen;
            final int scidLen = packet.getUnsignedByte(offset);
            checkCidLength(scidLen);
            ++offset;
            scid = sliceCid(packet, offset, scidLen);
            offset += scidLen;
            token = sliceToken(type, packet, offset, readable);
        }
        callback.process(sender, recipient, packet, type, version, scid, dcid, token);
    }
    
    private static void checkCidLength(final int length) throws QuicException {
        if (length > 20) {
            throw new QuicException("connection id to large: " + length + " > " + 20, QuicTransportError.PROTOCOL_VIOLATION);
        }
    }
    
    private static ByteBuf sliceToken(final QuicPacketType type, final ByteBuf packet, int offset, final int readable) throws QuicException {
        switch (type) {
            case INITIAL: {
                checkReadable(offset, readable, 1);
                final int numBytes = numBytesForVariableLengthInteger(packet.getByte(offset));
                final int len = (int)getVariableLengthInteger(packet, offset, numBytes);
                offset += numBytes;
                checkReadable(offset, readable, len);
                return packet.slice(offset, len);
            }
            case RETRY: {
                checkReadable(offset, readable, 16);
                final int tokenLen = readable - offset - 16;
                return packet.slice(offset, tokenLen);
            }
            default: {
                return Unpooled.EMPTY_BUFFER;
            }
        }
    }
    
    private static QuicException newProtocolViolationException(final String message) {
        return new QuicException(message, QuicTransportError.PROTOCOL_VIOLATION);
    }
    
    static ByteBuf sliceCid(final ByteBuf buffer, final int offset, final int len) throws QuicException {
        checkReadable(offset, buffer.readableBytes(), len);
        return buffer.slice(offset, len);
    }
    
    private static void checkReadable(final int offset, final int readable, final int needed) throws QuicException {
        final int r = readable - offset;
        if (r < needed) {
            throw newProtocolViolationException("Not enough bytes to read, " + r + " < " + needed);
        }
    }
    
    private static long getVariableLengthInteger(final ByteBuf in, final int offset, final int len) throws QuicException {
        checkReadable(offset, in.readableBytes(), len);
        switch (len) {
            case 1: {
                return in.getUnsignedByte(offset);
            }
            case 2: {
                return in.getUnsignedShort(offset) & 0x3FFF;
            }
            case 4: {
                return in.getUnsignedInt(offset) & 0x3FFFFFFFL;
            }
            case 8: {
                return in.getLong(offset) & 0x3FFFFFFFFFFFFFFFL;
            }
            default: {
                throw newProtocolViolationException("Unsupported length:" + len);
            }
        }
    }
    
    private static int numBytesForVariableLengthInteger(final byte b) {
        final byte val = (byte)(b >> 6);
        if ((val & 0x1) != 0x0) {
            if ((val & 0x2) != 0x0) {
                return 8;
            }
            return 2;
        }
        else {
            if ((val & 0x2) != 0x0) {
                return 4;
            }
            return 1;
        }
    }
    
    static boolean hasShortHeader(final byte b) {
        return (b & 0x80) == 0x0;
    }
    
    private static QuicPacketType typeOfLongHeader(final byte first, final long version) throws QuicException {
        if (version == 0L) {
            return QuicPacketType.VERSION_NEGOTIATION;
        }
        final int packetType = (first & 0x30) >> 4;
        switch (packetType) {
            case 0: {
                return QuicPacketType.INITIAL;
            }
            case 1: {
                return QuicPacketType.ZERO_RTT;
            }
            case 2: {
                return QuicPacketType.HANDSHAKE;
            }
            case 3: {
                return QuicPacketType.RETRY;
            }
            default: {
                throw newProtocolViolationException("Unknown packet type: " + packetType);
            }
        }
    }
    
    public interface QuicHeaderProcessor
    {
        void process(final InetSocketAddress p0, final InetSocketAddress p1, final ByteBuf p2, final QuicPacketType p3, final long p4, final ByteBuf p5, final ByteBuf p6, final ByteBuf p7) throws Exception;
    }
}
