alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (Decoder.java)

This example Java source code file (Decoder.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

charsequence, decode_decompression_exception, decode_illegal_index_value, dynamictable, empty_string, headerfield, hpack, illegalstateexception, indextype, ioexception, read_literal_header_name_length, read_literal_header_value_length, read_max_dynamic_table_size, state

The Decoder.java Java example source code

/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you under the Apache License,
 * version 2.0 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

/*
 * Copyright 2014 Twitter, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.netty.handler.codec.http2.internal.hpack;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http2.Http2Headers;
import io.netty.handler.codec.http2.internal.hpack.HpackUtil.IndexType;
import io.netty.util.internal.ThrowableUtil;
import io.netty.util.AsciiString;

import java.io.IOException;

import static io.netty.util.AsciiString.EMPTY_STRING;

import static java.lang.Math.min;

public final class Decoder {

    private static final IOException DECODE_DECOMPRESSION_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - decompression failure"), Decoder.class, "decode(...)");
    private static final IOException DECODE_ULE_128_DECOMPRESSION_EXCEPTION = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - decompression failure"), Decoder.class, "decodeULE128(...)");
    private static final IOException DECODE_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - illegal index value"), Decoder.class, "decode(...)");
    private static final IOException INDEX_HEADER_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - illegal index value"), Decoder.class, "indexHeader(...)");
    private static final IOException READ_NAME_ILLEGAL_INDEX_VALUE = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - illegal index value"), Decoder.class, "readName(...)");
    private static final IOException INVALID_MAX_DYNAMIC_TABLE_SIZE = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - invalid max dynamic table size"), Decoder.class, "setDynamicTableSize(...)");
    private static final IOException MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED = ThrowableUtil.unknownStackTrace(
            new IOException("HPACK - max dynamic table size change required"), Decoder.class, "decode(...)");

    private final DynamicTable dynamicTable;
    private final HuffmanDecoder huffmanDecoder;
    private final int maxHeaderSize;
    private int maxDynamicTableSize;
    private int encoderMaxDynamicTableSize;
    private boolean maxDynamicTableSizeChangeRequired;

    private long headerSize;
    private State state;
    private IndexType indexType;
    private int index;
    private boolean huffmanEncoded;
    private int skipLength;
    private int nameLength;
    private int valueLength;
    private CharSequence name;

    private enum State {
        READ_HEADER_REPRESENTATION,
        READ_MAX_DYNAMIC_TABLE_SIZE,
        READ_INDEXED_HEADER,
        READ_INDEXED_HEADER_NAME,
        READ_LITERAL_HEADER_NAME_LENGTH_PREFIX,
        READ_LITERAL_HEADER_NAME_LENGTH,
        READ_LITERAL_HEADER_NAME,
        SKIP_LITERAL_HEADER_NAME,
        READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX,
        READ_LITERAL_HEADER_VALUE_LENGTH,
        READ_LITERAL_HEADER_VALUE,
        SKIP_LITERAL_HEADER_VALUE
    }

    /**
     * Creates a new decoder.
     */
    public Decoder(int maxHeaderSize, int maxHeaderTableSize, int initialHuffmanDecodeCapacity) {
        dynamicTable = new DynamicTable(maxHeaderTableSize);
        this.maxHeaderSize = maxHeaderSize;
        maxDynamicTableSize = maxHeaderTableSize;
        encoderMaxDynamicTableSize = maxHeaderTableSize;
        maxDynamicTableSizeChangeRequired = false;
        huffmanDecoder = new HuffmanDecoder(initialHuffmanDecodeCapacity);
        reset();
    }

    private void reset() {
        headerSize = 0;
        state = State.READ_HEADER_REPRESENTATION;
        indexType = IndexType.NONE;
    }

    /**
     * Decode the header block into header fields.
     */
    public void decode(ByteBuf in, Http2Headers headers) throws IOException {
        while (in.isReadable()) {
            switch (state) {
                case READ_HEADER_REPRESENTATION:
                    byte b = in.readByte();
                    if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
                        // Encoder MUST signal maximum dynamic table size change
                        throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
                    }
                    if (b < 0) {
                        // Indexed Header Field
                        index = b & 0x7F;
                        if (index == 0) {
                            throw DECODE_ILLEGAL_INDEX_VALUE;
                        } else if (index == 0x7F) {
                            state = State.READ_INDEXED_HEADER;
                        } else {
                            indexHeader(index, headers);
                        }
                    } else if ((b & 0x40) == 0x40) {
                        // Literal Header Field with Incremental Indexing
                        indexType = IndexType.INCREMENTAL;
                        index = b & 0x3F;
                        if (index == 0) {
                            state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
                        } else if (index == 0x3F) {
                            state = State.READ_INDEXED_HEADER_NAME;
                        } else {
                            // Index was stored as the prefix
                            readName(index);
                            state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
                        }
                    } else if ((b & 0x20) == 0x20) {
                        // Dynamic Table Size Update
                        index = b & 0x1F;
                        if (index == 0x1F) {
                            state = State.READ_MAX_DYNAMIC_TABLE_SIZE;
                        } else {
                            setDynamicTableSize(index);
                            state = State.READ_HEADER_REPRESENTATION;
                        }
                    } else {
                        // Literal Header Field without Indexing / never Indexed
                        indexType = ((b & 0x10) == 0x10) ? IndexType.NEVER : IndexType.NONE;
                        index = b & 0x0F;
                        if (index == 0) {
                            state = State.READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
                        } else if (index == 0x0F) {
                            state = State.READ_INDEXED_HEADER_NAME;
                        } else {
                            // Index was stored as the prefix
                            readName(index);
                            state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
                        }
                    }
                    break;

                case READ_MAX_DYNAMIC_TABLE_SIZE:
                    int maxSize = decodeULE128(in);
                    if (maxSize == -1) {
                        return;
                    }

                    // Check for numerical overflow
                    if (maxSize > Integer.MAX_VALUE - index) {
                        throw DECODE_DECOMPRESSION_EXCEPTION;
                    }

                    setDynamicTableSize(index + maxSize);
                    state = State.READ_HEADER_REPRESENTATION;
                    break;

                case READ_INDEXED_HEADER:
                    int headerIndex = decodeULE128(in);
                    if (headerIndex == -1) {
                        return;
                    }

                    // Check for numerical overflow
                    if (headerIndex > Integer.MAX_VALUE - index) {
                        throw DECODE_DECOMPRESSION_EXCEPTION;
                    }

                    indexHeader(index + headerIndex, headers);
                    state = State.READ_HEADER_REPRESENTATION;
                    break;

                case READ_INDEXED_HEADER_NAME:
                    // Header Name matches an entry in the Header Table
                    int nameIndex = decodeULE128(in);
                    if (nameIndex == -1) {
                        return;
                    }

                    // Check for numerical overflow
                    if (nameIndex > Integer.MAX_VALUE - index) {
                        throw DECODE_DECOMPRESSION_EXCEPTION;
                    }

                    readName(index + nameIndex);
                    state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
                    break;

                case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:
                    b = in.readByte();
                    huffmanEncoded = (b & 0x80) == 0x80;
                    index = b & 0x7F;
                    if (index == 0x7f) {
                        state = State.READ_LITERAL_HEADER_NAME_LENGTH;
                    } else {
                        nameLength = index;

                        // Check name length against max header size
                        if (exceedsMaxHeaderSize(nameLength)) {

                            if (indexType == IndexType.NONE) {
                                // Name is unused so skip bytes
                                name = EMPTY_STRING;
                                skipLength = nameLength;
                                state = State.SKIP_LITERAL_HEADER_NAME;
                                break;
                            }

                            // Check name length against max dynamic table size
                            if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
                                dynamicTable.clear();
                                name = EMPTY_STRING;
                                skipLength = nameLength;
                                state = State.SKIP_LITERAL_HEADER_NAME;
                                break;
                            }
                        }
                        state = State.READ_LITERAL_HEADER_NAME;
                    }
                    break;

                case READ_LITERAL_HEADER_NAME_LENGTH:
                    // Header Name is a Literal String
                    nameLength = decodeULE128(in);
                    if (nameLength == -1) {
                        return;
                    }

                    // Check for numerical overflow
                    if (nameLength > Integer.MAX_VALUE - index) {
                        throw DECODE_DECOMPRESSION_EXCEPTION;
                    }
                    nameLength += index;

                    // Check name length against max header size
                    if (exceedsMaxHeaderSize(nameLength)) {
                        if (indexType == IndexType.NONE) {
                            // Name is unused so skip bytes
                            name = EMPTY_STRING;
                            skipLength = nameLength;
                            state = State.SKIP_LITERAL_HEADER_NAME;
                            break;
                        }

                        // Check name length against max dynamic table size
                        if (nameLength + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
                            dynamicTable.clear();
                            name = EMPTY_STRING;
                            skipLength = nameLength;
                            state = State.SKIP_LITERAL_HEADER_NAME;
                            break;
                        }
                    }
                    state = State.READ_LITERAL_HEADER_NAME;
                    break;

                case READ_LITERAL_HEADER_NAME:
                    // Wait until entire name is readable
                    if (in.readableBytes() < nameLength) {
                        return;
                    }

                    name = readStringLiteral(in, nameLength);

                    state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
                    break;

                case SKIP_LITERAL_HEADER_NAME:
                    int skip = min(in.readableBytes(), skipLength);
                    in.skipBytes(skip);
                    skipLength -= skip;

                    if (skipLength == 0) {
                        state = State.READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
                    }
                    break;

                case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
                    b = in.readByte();
                    huffmanEncoded = (b & 0x80) == 0x80;
                    index = b & 0x7F;
                    if (index == 0x7f) {
                        state = State.READ_LITERAL_HEADER_VALUE_LENGTH;
                    } else {
                        valueLength = index;

                        // Check new header size against max header size
                        long newHeaderSize = (long) nameLength + (long) valueLength;
                        if (exceedsMaxHeaderSize(newHeaderSize)) {
                            // truncation will be reported during endHeaderBlock
                            headerSize = maxHeaderSize + 1;

                            if (indexType == IndexType.NONE) {
                                // Value is unused so skip bytes
                                state = State.SKIP_LITERAL_HEADER_VALUE;
                                break;
                            }

                            // Check new header size against max dynamic table size
                            if (newHeaderSize + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
                                dynamicTable.clear();
                                state = State.SKIP_LITERAL_HEADER_VALUE;
                                break;
                            }
                        }

                        if (valueLength == 0) {
                            insertHeader(headers, name, EMPTY_STRING, indexType);
                            state = State.READ_HEADER_REPRESENTATION;
                        } else {
                            state = State.READ_LITERAL_HEADER_VALUE;
                        }
                    }

                    break;

                case READ_LITERAL_HEADER_VALUE_LENGTH:
                    // Header Value is a Literal String
                    valueLength = decodeULE128(in);
                    if (valueLength == -1) {
                        return;
                    }

                    // Check for numerical overflow
                    if (valueLength > Integer.MAX_VALUE - index) {
                        throw DECODE_DECOMPRESSION_EXCEPTION;
                    }
                    valueLength += index;

                    // Check new header size against max header size
                    long newHeaderSize = (long) nameLength + (long) valueLength;
                    if (newHeaderSize + headerSize > maxHeaderSize) {
                        // truncation will be reported during endHeaderBlock
                        headerSize = maxHeaderSize + 1;

                        if (indexType == IndexType.NONE) {
                            // Value is unused so skip bytes
                            state = State.SKIP_LITERAL_HEADER_VALUE;
                            break;
                        }

                        // Check new header size against max dynamic table size
                        if (newHeaderSize + HeaderField.HEADER_ENTRY_OVERHEAD > dynamicTable.capacity()) {
                            dynamicTable.clear();
                            state = State.SKIP_LITERAL_HEADER_VALUE;
                            break;
                        }
                    }
                    state = State.READ_LITERAL_HEADER_VALUE;
                    break;

                case READ_LITERAL_HEADER_VALUE:
                    // Wait until entire value is readable
                    if (in.readableBytes() < valueLength) {
                        return;
                    }

                    CharSequence value = readStringLiteral(in, valueLength);
                    insertHeader(headers, name, value, indexType);
                    state = State.READ_HEADER_REPRESENTATION;
                    break;

                case SKIP_LITERAL_HEADER_VALUE:
                    int skipBytes = min(in.readableBytes(), valueLength);
                    in.skipBytes(skipBytes);
                    valueLength -= skipBytes;
                    if (valueLength == 0) {
                        state = State.READ_HEADER_REPRESENTATION;
                    }
                    break;

                default:
                    throw new IllegalStateException("should not reach here");
            }
        }
    }

    /**
     * End the current header block. Returns if the header field has been truncated. This must be
     * called after the header block has been completely decoded.
     */
    public boolean endHeaderBlock() {
        boolean truncated = headerSize > maxHeaderSize;
        reset();
        return truncated;
    }

    /**
     * Set the maximum table size. If this is below the maximum size of the dynamic table used by
     * the encoder, the beginning of the next header block MUST signal this change.
     */
    public void setMaxHeaderTableSize(int maxHeaderTableSize) {
        maxDynamicTableSize = maxHeaderTableSize;
        if (maxDynamicTableSize < encoderMaxDynamicTableSize) {
            // decoder requires less space than encoder
            // encoder MUST signal this change
            maxDynamicTableSizeChangeRequired = true;
            dynamicTable.setCapacity(maxDynamicTableSize);
        }
    }

    /**
     * Return the maximum table size. This is the maximum size allowed by both the encoder and the
     * decoder.
     */
    public int getMaxHeaderTableSize() {
        return dynamicTable.capacity();
    }

    /**
     * Return the number of header fields in the dynamic table. Exposed for testing.
     */
    int length() {
        return dynamicTable.length();
    }

    /**
     * Return the size of the dynamic table. Exposed for testing.
     */
    int size() {
        return dynamicTable.size();
    }

    /**
     * Return the header field at the given index. Exposed for testing.
     */
    HeaderField getHeaderField(int index) {
        return dynamicTable.getEntry(index + 1);
    }

    private void setDynamicTableSize(int dynamicTableSize) throws IOException {
        if (dynamicTableSize > maxDynamicTableSize) {
            throw INVALID_MAX_DYNAMIC_TABLE_SIZE;
        }
        encoderMaxDynamicTableSize = dynamicTableSize;
        maxDynamicTableSizeChangeRequired = false;
        dynamicTable.setCapacity(dynamicTableSize);
    }

    private void readName(int index) throws IOException {
        if (index <= StaticTable.length) {
            HeaderField headerField = StaticTable.getEntry(index);
            name = headerField.name;
        } else if (index - StaticTable.length <= dynamicTable.length()) {
            HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
            name = headerField.name;
        } else {
            throw READ_NAME_ILLEGAL_INDEX_VALUE;
        }
    }

    private void indexHeader(int index, Http2Headers headers) throws IOException {
        if (index <= StaticTable.length) {
            HeaderField headerField = StaticTable.getEntry(index);
            addHeader(headers, headerField.name, headerField.value);
        } else if (index - StaticTable.length <= dynamicTable.length()) {
            HeaderField headerField = dynamicTable.getEntry(index - StaticTable.length);
            addHeader(headers, headerField.name, headerField.value);
        } else {
            throw INDEX_HEADER_ILLEGAL_INDEX_VALUE;
        }
    }

    private void insertHeader(Http2Headers headers, CharSequence name, CharSequence value,
                              IndexType indexType) {
        addHeader(headers, name, value);

        switch (indexType) {
            case NONE:
            case NEVER:
                break;

            case INCREMENTAL:
                dynamicTable.add(new HeaderField(name, value));
                break;

            default:
                throw new IllegalStateException("should not reach here");
        }
    }

    private void addHeader(Http2Headers headers, CharSequence name, CharSequence value) {
        long newSize = headerSize + name.length() + value.length();
        if (newSize <= maxHeaderSize) {
            headers.add(name, value);
            headerSize = (int) newSize;
        } else {
            // truncation will be reported during endHeaderBlock
            headerSize = maxHeaderSize + 1;
        }
    }

    private boolean exceedsMaxHeaderSize(long size) {
        // Check new header size against max header size
        if (size + headerSize <= maxHeaderSize) {
            return false;
        }

        // truncation will be reported during endHeaderBlock
        headerSize = maxHeaderSize + 1;
        return true;
    }

    private CharSequence readStringLiteral(ByteBuf in, int length) throws IOException {
        if (huffmanEncoded) {
            return huffmanDecoder.decode(in, length);
        } else {
            byte[] buf = new byte[length];
            in.readBytes(buf);
            return new AsciiString(buf, false);
        }
    }

    // Unsigned Little Endian Base 128 Variable-Length Integer Encoding
    private static int decodeULE128(ByteBuf in) throws IOException {
        in.markReaderIndex();
        int result = 0;
        int shift = 0;
        while (shift < 32) {
            if (!in.isReadable()) {
                // Buffer does not contain entire integer,
                // reset reader index and return -1.
                in.resetReaderIndex();
                return -1;
            }
            byte b = in.readByte();
            if (shift == 28 && (b & 0xF8) != 0) {
                break;
            }
            result |= (b & 0x7F) << shift;
            if ((b & 0x80) == 0) {
                return result;
            }
            shift += 7;
        }
        // Value exceeds Integer.MAX_VALUE
        in.resetReaderIndex();
        throw DECODE_ULE_128_DECOMPRESSION_EXCEPTION;
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java Decoder.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.