class HpackDecoder {

    public MetaData decode(ByteBuffer buffer) throws HpackException.SessionException, HpackException.StreamException {
        if (LOG.isDebugEnabled())
            LOG.debug(String.format("CtxTbl[%x] decoding %d octets", context.hashCode(), buffer.remaining()));

        // If the buffer is big, don't even think about decoding it
        if (buffer.remaining() > builder.getMaxSize())
            throw new HpackException.SessionException("431 Request Header Fields too large");

        boolean emitted = false;

        while (buffer.hasRemaining()) {
            if (LOG.isDebugEnabled() && buffer.hasArray()) {
                int l = Math.min(buffer.remaining(), 32);
                LOG.debug("decode {}{}",
                        TypeUtils.toHexString(buffer.array(), buffer.arrayOffset() + buffer.position(), l),
                        l < buffer.remaining() ? "..." : "");
            }

            byte b = buffer.get();
            if (b < 0) {
                // 7.1 indexed if the high bit is set
                int index = NBitInteger.decode(buffer, 7);
                Entry entry = context.get(index);
                if (entry == null)
                    throw new HpackException.SessionException("Unknown index %d", index);

                if (entry.isStatic()) {
                    if (LOG.isDebugEnabled())
                        LOG.debug("decode IdxStatic {}", entry);
                    // emit field
                    emitted = true;
                    builder.emit(entry.getHttpField());

                    // TODO copy and add to reference set if there is room
                    // _context.add(entry.getHttpField());
                } else {
                    if (LOG.isDebugEnabled())
                        LOG.debug("decode Idx {}", entry);
                    // emit
                    emitted = true;
                    builder.emit(entry.getHttpField());
                }
            } else {
                // look at the first nibble in detail
                byte f = (byte) ((b & 0xF0) >> 4);
                String name;
                HttpHeader header;
                String value;

                boolean indexed;
                int nameIndex;

                switch (f) {
                    case 2: // 7.3
                    case 3: // 7.3
                        // change table size
                        int size = NBitInteger.decode(buffer, 5);
                        if (LOG.isDebugEnabled())
                            LOG.debug("decode resize=" + size);
                        if (size > localMaxDynamicTableSize)
                            throw new IllegalArgumentException();
                        if (emitted)
                            throw new HpackException.CompressionException("Dynamic table resize after fields");
                        context.resize(size);
                        continue;

                    case 0: // 7.2.2
                    case 1: // 7.2.3
                        indexed = false;
                        nameIndex = NBitInteger.decode(buffer, 4);
                        break;

                    case 4: // 7.2.1
                    case 5: // 7.2.1
                    case 6: // 7.2.1
                    case 7: // 7.2.1
                        indexed = true;
                        nameIndex = NBitInteger.decode(buffer, 6);
                        break;

                    default:
                        throw new IllegalStateException();
                }

                boolean huffmanName = false;

                // decode the name
                if (nameIndex > 0) {
                    Entry nameEntry = context.get(nameIndex);
                    name = nameEntry.getHttpField().getName();
                    header = nameEntry.getHttpField().getHeader();
                } else {
                    huffmanName = (buffer.get() & 0x80) == 0x80;
                    int length = NBitInteger.decode(buffer, 7);
                    builder.checkSize(length, huffmanName);
                    if (huffmanName)
                        name = Huffman.decode(buffer, length);
                    else
                        name = toASCIIString(buffer, length);
                    check:
                    for (int i = name.length(); i-- > 0; ) {
                        char c = name.charAt(i);
                        if (c > 0xff) {
                            builder.streamException("Illegal header name %s", name);
                            break;
                        }
                        HttpTokens.Token token = HttpTokens.TOKENS[0xFF & c];
                        switch (token.getType()) {
                            case ALPHA:
                                if (c >= 'A' && c <= 'Z') {
                                    builder.streamException("Uppercase header name %s", name);
                                    break check;
                                }
                                break;

                            case COLON:
                            case TCHAR:
                            case DIGIT:
                                break;

                            default:
                                builder.streamException("Illegal header name %s", name);
                                break check;
                        }
                    }
                    header = HttpHeader.CACHE.get(name);
                }

                // decode the value
                boolean huffmanValue = (buffer.get() & 0x80) == 0x80;
                int length = NBitInteger.decode(buffer, 7);
                builder.checkSize(length, huffmanValue);
                if (huffmanValue)
                    value = Huffman.decode(buffer, length);
                else
                    value = toASCIIString(buffer, length);

                // Make the new field
                HttpField field;
                if (header == null) {
                    // just make a normal field and bypass header name lookup
                    field = new HttpField(null, name, value);
                } else {
                    // might be worthwhile to create a value HttpField if it is indexed
                    // and/or of a type that may be looked up multiple times.
                    switch (header) {
                        case C_STATUS:
                            if (indexed)
                                field = new HttpField.IntValueHttpField(header, name, value);
                            else
                                field = new HttpField(header, name, value);
                            break;

                        case C_AUTHORITY:
                            field = new AuthorityHttpField(value);
                            break;

                        case CONTENT_LENGTH:
                            if ("0".equals(value))
                                field = CONTENT_LENGTH_0;
                            else
                                field = new HttpField.LongValueHttpField(header, name, value);
                            break;

                        default:
                            field = new HttpField(header, name, value);
                            break;
                    }
                }

                if (LOG.isDebugEnabled()) {
                    LOG.debug("decoded '{}' by {}/{}/{}",
                            field,
                            nameIndex > 0 ? "IdxName" : (huffmanName ? "HuffName" : "LitName"),
                            huffmanValue ? "HuffVal" : "LitVal",
                            indexed ? "Idx" : "");
                }

                // emit the field
                emitted = true;
                builder.emit(field);

                // if indexed add to dynamic table
                if (indexed)
                    context.add(field);
            }
        }

        return builder.build();
    }

    public  HpackDecoder(int localMaxDynamicTableSize, int maxHeaderSize);

}