All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.grpc.okhttp.internal.framed.Hpack Maven / Gradle / Ivy

There is a newer version: 1.68.1
Show newest version
/*
 * Copyright (C) 2013 Square, 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.
 */
/*
 * Forked from OkHttp 2.5.0
 */

package io.grpc.okhttp.internal.framed;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import okio.Buffer;
import okio.BufferedSource;
import okio.ByteString;
import okio.Okio;
import okio.Source;

/**
 * Read and write HPACK v10.
 *
 * http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12
 *
 * This implementation uses an array for the dynamic table and a list for
 * indexed entries.  Dynamic entries are added to the array, starting in the
 * last position moving forward.  When the array fills, it is doubled.
 */
final class Hpack {
  private static final int PREFIX_4_BITS = 0x0f;
  private static final int PREFIX_5_BITS = 0x1f;
  private static final int PREFIX_6_BITS = 0x3f;
  private static final int PREFIX_7_BITS = 0x7f;

  private static final ByteString PSEUDO_PREFIX = ByteString.encodeUtf8(":");

  private static final int SETTINGS_HEADER_TABLE_SIZE = 4_096;

  /**
   * The decoder has ultimate control of the maximum size of the dynamic table but we can choose
   * to use less. We'll put a cap at 16K. This is arbitrary but should be enough for most purposes.
   */
  private static final int SETTINGS_HEADER_TABLE_SIZE_LIMIT = 16_384;

  private static final io.grpc.okhttp.internal.framed.Header[] STATIC_HEADER_TABLE = new io.grpc.okhttp.internal.framed.Header[] {
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_AUTHORITY, ""),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_METHOD, "GET"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_METHOD, "POST"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_PATH, "/"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_PATH, "/index.html"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_SCHEME, "http"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.TARGET_SCHEME, "https"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "200"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "204"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "206"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "304"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "400"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "404"),
      new io.grpc.okhttp.internal.framed.Header(io.grpc.okhttp.internal.framed.Header.RESPONSE_STATUS, "500"),
      new io.grpc.okhttp.internal.framed.Header("accept-charset", ""),
      new io.grpc.okhttp.internal.framed.Header("accept-encoding", "gzip, deflate"),
      new io.grpc.okhttp.internal.framed.Header("accept-language", ""),
      new io.grpc.okhttp.internal.framed.Header("accept-ranges", ""),
      new io.grpc.okhttp.internal.framed.Header("accept", ""),
      new io.grpc.okhttp.internal.framed.Header("access-control-allow-origin", ""),
      new io.grpc.okhttp.internal.framed.Header("age", ""),
      new io.grpc.okhttp.internal.framed.Header("allow", ""),
      new io.grpc.okhttp.internal.framed.Header("authorization", ""),
      new io.grpc.okhttp.internal.framed.Header("cache-control", ""),
      new io.grpc.okhttp.internal.framed.Header("content-disposition", ""),
      new io.grpc.okhttp.internal.framed.Header("content-encoding", ""),
      new io.grpc.okhttp.internal.framed.Header("content-language", ""),
      new io.grpc.okhttp.internal.framed.Header("content-length", ""),
      new io.grpc.okhttp.internal.framed.Header("content-location", ""),
      new io.grpc.okhttp.internal.framed.Header("content-range", ""),
      new io.grpc.okhttp.internal.framed.Header("content-type", ""),
      new io.grpc.okhttp.internal.framed.Header("cookie", ""),
      new io.grpc.okhttp.internal.framed.Header("date", ""),
      new io.grpc.okhttp.internal.framed.Header("etag", ""),
      new io.grpc.okhttp.internal.framed.Header("expect", ""),
      new io.grpc.okhttp.internal.framed.Header("expires", ""),
      new io.grpc.okhttp.internal.framed.Header("from", ""),
      new io.grpc.okhttp.internal.framed.Header("host", ""),
      new io.grpc.okhttp.internal.framed.Header("if-match", ""),
      new io.grpc.okhttp.internal.framed.Header("if-modified-since", ""),
      new io.grpc.okhttp.internal.framed.Header("if-none-match", ""),
      new io.grpc.okhttp.internal.framed.Header("if-range", ""),
      new io.grpc.okhttp.internal.framed.Header("if-unmodified-since", ""),
      new io.grpc.okhttp.internal.framed.Header("last-modified", ""),
      new io.grpc.okhttp.internal.framed.Header("link", ""),
      new io.grpc.okhttp.internal.framed.Header("location", ""),
      new io.grpc.okhttp.internal.framed.Header("max-forwards", ""),
      new io.grpc.okhttp.internal.framed.Header("proxy-authenticate", ""),
      new io.grpc.okhttp.internal.framed.Header("proxy-authorization", ""),
      new io.grpc.okhttp.internal.framed.Header("range", ""),
      new io.grpc.okhttp.internal.framed.Header("referer", ""),
      new io.grpc.okhttp.internal.framed.Header("refresh", ""),
      new io.grpc.okhttp.internal.framed.Header("retry-after", ""),
      new io.grpc.okhttp.internal.framed.Header("server", ""),
      new io.grpc.okhttp.internal.framed.Header("set-cookie", ""),
      new io.grpc.okhttp.internal.framed.Header("strict-transport-security", ""),
      new io.grpc.okhttp.internal.framed.Header("transfer-encoding", ""),
      new io.grpc.okhttp.internal.framed.Header("user-agent", ""),
      new io.grpc.okhttp.internal.framed.Header("vary", ""),
      new io.grpc.okhttp.internal.framed.Header("via", ""),
      new io.grpc.okhttp.internal.framed.Header("www-authenticate", "")
  };

  private Hpack() {
  }

  // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-3.1
  static final class Reader {

    private final List headerList = new ArrayList<>();
    private final BufferedSource source;

    private int headerTableSizeSetting;
    private int maxDynamicTableByteCount;
    // Visible for testing.
    io.grpc.okhttp.internal.framed.Header[] dynamicTable = new io.grpc.okhttp.internal.framed.Header[8];
    // Array is populated back to front, so new entries always have lowest index.
    int nextDynamicTableIndex = dynamicTable.length - 1;
    int dynamicTableHeaderCount = 0;
    int dynamicTableByteCount = 0;

    Reader(int headerTableSizeSetting, Source source) {
      this(headerTableSizeSetting, headerTableSizeSetting, source);
    }

    // Visible for testing.
    Reader(int headerTableSizeSetting, int maxDynamicTableByteCount, Source source) {
      this.headerTableSizeSetting = headerTableSizeSetting;
      this.maxDynamicTableByteCount = maxDynamicTableByteCount;
      this.source = Okio.buffer(source);
    }

    int maxDynamicTableByteCount() {
      return maxDynamicTableByteCount;
    }

    /**
     * Called by the reader when the peer sent {@link Settings#HEADER_TABLE_SIZE}.
     * While this establishes the maximum dynamic table size, the
     * {@link #maxDynamicTableByteCount} set during processing may limit the
     * table size to a smaller amount.
     * 

Evicts entries or clears the table as needed. */ void headerTableSizeSetting(int headerTableSizeSetting) { this.headerTableSizeSetting = headerTableSizeSetting; this.maxDynamicTableByteCount = headerTableSizeSetting; adjustDynamicTableByteCount(); } private void adjustDynamicTableByteCount() { if (maxDynamicTableByteCount < dynamicTableByteCount) { if (maxDynamicTableByteCount == 0) { clearDynamicTable(); } else { evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount); } } } private void clearDynamicTable() { Arrays.fill(dynamicTable, null); nextDynamicTableIndex = dynamicTable.length - 1; dynamicTableHeaderCount = 0; dynamicTableByteCount = 0; } /** Returns the count of entries evicted. */ private int evictToRecoverBytes(int bytesToRecover) { int entriesToEvict = 0; if (bytesToRecover > 0) { // determine how many headers need to be evicted. for (int j = dynamicTable.length - 1; j >= nextDynamicTableIndex && bytesToRecover > 0; j--) { bytesToRecover -= dynamicTable[j].hpackSize; dynamicTableByteCount -= dynamicTable[j].hpackSize; dynamicTableHeaderCount--; entriesToEvict++; } System.arraycopy(dynamicTable, nextDynamicTableIndex + 1, dynamicTable, nextDynamicTableIndex + 1 + entriesToEvict, dynamicTableHeaderCount); nextDynamicTableIndex += entriesToEvict; } return entriesToEvict; } /** * Read {@code byteCount} bytes of headers from the source stream. This * implementation does not propagate the never indexed flag of a header. */ void readHeaders() throws IOException { while (!source.exhausted()) { int b = source.readByte() & 0xff; if (b == 0x80) { // 10000000 throw new IOException("index == 0"); } else if ((b & 0x80) == 0x80) { // 1NNNNNNN int index = readInt(b, PREFIX_7_BITS); readIndexedHeader(index - 1); } else if (b == 0x40) { // 01000000 readLiteralHeaderWithIncrementalIndexingNewName(); } else if ((b & 0x40) == 0x40) { // 01NNNNNN int index = readInt(b, PREFIX_6_BITS); readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1); } else if ((b & 0x20) == 0x20) { // 001NNNNN maxDynamicTableByteCount = readInt(b, PREFIX_5_BITS); if (maxDynamicTableByteCount < 0 || maxDynamicTableByteCount > headerTableSizeSetting) { throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount); } adjustDynamicTableByteCount(); } else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit. readLiteralHeaderWithoutIndexingNewName(); } else { // 000?NNNN - Ignore never indexed bit. int index = readInt(b, PREFIX_4_BITS); readLiteralHeaderWithoutIndexingIndexedName(index - 1); } } } public List getAndResetHeaderList() { List result = new ArrayList<>(headerList); headerList.clear(); return result; } private void readIndexedHeader(int index) throws IOException { if (isStaticHeader(index)) { io.grpc.okhttp.internal.framed.Header staticEntry = STATIC_HEADER_TABLE[index]; headerList.add(staticEntry); } else { int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length); if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) { throw new IOException("Header index too large " + (index + 1)); } headerList.add(dynamicTable[dynamicTableIndex]); } } // referencedHeaders is relative to nextDynamicTableIndex + 1. private int dynamicTableIndex(int index) { return nextDynamicTableIndex + 1 + index; } private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException { ByteString name = getName(index); ByteString value = readByteString(); headerList.add(new io.grpc.okhttp.internal.framed.Header(name, value)); } private void readLiteralHeaderWithoutIndexingNewName() throws IOException { ByteString name = checkLowercase(readByteString()); ByteString value = readByteString(); headerList.add(new io.grpc.okhttp.internal.framed.Header(name, value)); } private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex) throws IOException { ByteString name = getName(nameIndex); ByteString value = readByteString(); insertIntoDynamicTable(-1, new io.grpc.okhttp.internal.framed.Header(name, value)); } private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException { ByteString name = checkLowercase(readByteString()); ByteString value = readByteString(); insertIntoDynamicTable(-1, new io.grpc.okhttp.internal.framed.Header(name, value)); } private ByteString getName(int index) throws IOException { if (isStaticHeader(index)) { return STATIC_HEADER_TABLE[index].name; } else { int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length); if (dynamicTableIndex < 0 || dynamicTableIndex >= dynamicTable.length) { throw new IOException("Header index too large " + (index + 1)); } return dynamicTable[dynamicTableIndex].name; } } private boolean isStaticHeader(int index) { return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1; } /** index == -1 when new. */ private void insertIntoDynamicTable(int index, io.grpc.okhttp.internal.framed.Header entry) { headerList.add(entry); int delta = entry.hpackSize; if (index != -1) { // Index -1 == new header. delta -= dynamicTable[dynamicTableIndex(index)].hpackSize; } // if the new or replacement header is too big, drop all entries. if (delta > maxDynamicTableByteCount) { clearDynamicTable(); return; } // Evict headers to the required length. int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount; int entriesEvicted = evictToRecoverBytes(bytesToRecover); if (index == -1) { // Adding a value to the dynamic table. if (dynamicTableHeaderCount + 1 > dynamicTable.length) { // Need to grow the dynamic table. io.grpc.okhttp.internal.framed.Header[] doubled = new io.grpc.okhttp.internal.framed.Header[dynamicTable.length * 2]; System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length); nextDynamicTableIndex = dynamicTable.length - 1; dynamicTable = doubled; } index = nextDynamicTableIndex--; dynamicTable[index] = entry; dynamicTableHeaderCount++; } else { // Replace value at same position. index += dynamicTableIndex(index) + entriesEvicted; dynamicTable[index] = entry; } dynamicTableByteCount += delta; } private int readByte() throws IOException { return source.readByte() & 0xff; } int readInt(int firstByte, int prefixMask) throws IOException { int prefix = firstByte & prefixMask; if (prefix < prefixMask) { return prefix; // This was a single byte value. } // This is a multibyte value. Read 7 bits at a time. int result = prefixMask; int shift = 0; while (true) { int b = readByte(); if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255]. result += (b & 0x7f) << shift; shift += 7; } else { result += b << shift; // Last byte. break; } } return result; } /** Reads a potentially Huffman encoded byte string. */ ByteString readByteString() throws IOException { int firstByte = readByte(); boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNN int length = readInt(firstByte, PREFIX_7_BITS); if (huffmanDecode) { return ByteString.of(io.grpc.okhttp.internal.framed.Huffman.get().decode(source.readByteArray(length))); } else { return source.readByteString(length); } } } private static final Map NAME_TO_FIRST_INDEX = nameToFirstIndex(); private static Map nameToFirstIndex() { Map result = new LinkedHashMap<>(STATIC_HEADER_TABLE.length); for (int i = 0; i < STATIC_HEADER_TABLE.length; i++) { if (!result.containsKey(STATIC_HEADER_TABLE[i].name)) { result.put(STATIC_HEADER_TABLE[i].name, i); } } return Collections.unmodifiableMap(result); } static final class Writer { private final Buffer out; private boolean useCompression; // Visible for testing. int headerTableSizeSetting; /** * In the scenario where the dynamic table size changes multiple times between transmission of * header blocks, we need to keep track of the smallest value in that interval. */ private int smallestHeaderTableSizeSetting = Integer.MAX_VALUE; private boolean emitDynamicTableSizeUpdate; private int maxDynamicTableByteCount; // Visible for testing. io.grpc.okhttp.internal.framed.Header[] dynamicTable = new io.grpc.okhttp.internal.framed.Header[8]; // Visible for testing. int dynamicTableHeaderCount; // Array is populated back to front, so new entries always have lowest index. private int nextDynamicTableIndex = dynamicTable.length - 1; private int dynamicTableByteCount; // Disable Huffman encoding as for the CPU vs bandwidth trade-off. Writer(Buffer out) { this(SETTINGS_HEADER_TABLE_SIZE, false, out); } // Visible for testing. Writer(int headerTableSizeSetting, boolean useCompression, Buffer out) { this.headerTableSizeSetting = headerTableSizeSetting; this.maxDynamicTableByteCount = headerTableSizeSetting; this.useCompression = useCompression; this.out = out; } /** This does not use "never indexed" semantics for sensitive headers. */ // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3 void writeHeaders(List headerBlock) throws IOException { if (emitDynamicTableSizeUpdate) { if (smallestHeaderTableSizeSetting < maxDynamicTableByteCount) { // Multiple dynamic table size updates! writeInt(smallestHeaderTableSizeSetting, PREFIX_5_BITS, 0x20); } emitDynamicTableSizeUpdate = false; smallestHeaderTableSizeSetting = Integer.MAX_VALUE; writeInt(maxDynamicTableByteCount, PREFIX_5_BITS, 0x20); } for (int i = 0, size = headerBlock.size(); i < size; i++) { io.grpc.okhttp.internal.framed.Header header = headerBlock.get(i); ByteString name = header.name.toAsciiLowercase(); ByteString value = header.value; int headerIndex = -1; int headerNameIndex = -1; Integer staticIndex = NAME_TO_FIRST_INDEX.get(name); if (staticIndex != null) { headerNameIndex = staticIndex + 1; if (headerNameIndex >= 2 && headerNameIndex <= 7) { // Only search a subset of the static header table. Most entries have an empty value, so // it's unnecessary to waste cycles looking at them. This check is built on the // observation that the header entries we care about are in adjacent pairs, and we // always know the first index of the pair. if (STATIC_HEADER_TABLE[headerNameIndex - 1].value.equals(value)) { headerIndex = headerNameIndex; } else if (STATIC_HEADER_TABLE[headerNameIndex].value.equals(value)) { headerIndex = headerNameIndex + 1; } } } if (headerIndex == -1) { for (int j = nextDynamicTableIndex + 1; j < dynamicTable.length; j++) { if (dynamicTable[j].name.equals(name)) { if (dynamicTable[j].value.equals(value)) { headerIndex = j - nextDynamicTableIndex + STATIC_HEADER_TABLE.length; break; } else if (headerNameIndex == -1) { headerNameIndex = j - nextDynamicTableIndex + STATIC_HEADER_TABLE.length; } } } } if (headerIndex != -1) { // Indexed Header Field. writeInt(headerIndex, PREFIX_7_BITS, 0x80); } else if (headerNameIndex == -1) { // Literal Header Field with Incremental Indexing - New Name. out.writeByte(0x40); writeByteString(name); writeByteString(value); insertIntoDynamicTable(header); } else if (name.startsWith(PSEUDO_PREFIX) && !io.grpc.okhttp.internal.framed.Header.TARGET_AUTHORITY.equals(name)) { // Follow Chromes lead - only include the :authority pseudo header, but exclude all other // pseudo headers. Literal Header Field without Indexing - Indexed Name. writeInt(headerNameIndex, PREFIX_4_BITS, 0); writeByteString(value); } else { // Literal Header Field with Incremental Indexing - Indexed Name. writeInt(headerNameIndex, PREFIX_6_BITS, 0x40); writeByteString(value); insertIntoDynamicTable(header); } } } // http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1 void writeInt(int value, int prefixMask, int bits) throws IOException { // Write the raw value for a single byte value. if (value < prefixMask) { out.writeByte(bits | value); return; } // Write the mask to start a multibyte value. out.writeByte(bits | prefixMask); value -= prefixMask; // Write 7 bits at a time 'til we're done. while (value >= 0x80) { int b = value & 0x7f; out.writeByte(b | 0x80); value >>>= 7; } out.writeByte(value); } void writeByteString(ByteString data) throws IOException { if (useCompression && io.grpc.okhttp.internal.framed.Huffman.get().encodedLength(data.toByteArray()) < data.size()) { Buffer huffmanBuffer = new Buffer(); io.grpc.okhttp.internal.framed.Huffman.get().encode(data.toByteArray(), huffmanBuffer.outputStream()); ByteString huffmanBytes = huffmanBuffer.readByteString(); writeInt(huffmanBytes.size(), PREFIX_7_BITS, 0x80); out.write(huffmanBytes); } else { writeInt(data.size(), PREFIX_7_BITS, 0); out.write(data); } } int maxDynamicTableByteCount() { return maxDynamicTableByteCount; } private void clearDynamicTable() { Arrays.fill(dynamicTable, null); nextDynamicTableIndex = dynamicTable.length - 1; dynamicTableHeaderCount = 0; dynamicTableByteCount = 0; } /** Returns the count of entries evicted. */ private int evictToRecoverBytes(int bytesToRecover) { int entriesToEvict = 0; if (bytesToRecover > 0) { // determine how many headers need to be evicted. for (int j = dynamicTable.length - 1; j >= nextDynamicTableIndex && bytesToRecover > 0; j--) { bytesToRecover -= dynamicTable[j].hpackSize; dynamicTableByteCount -= dynamicTable[j].hpackSize; dynamicTableHeaderCount--; entriesToEvict++; } System.arraycopy(dynamicTable, nextDynamicTableIndex + 1, dynamicTable, nextDynamicTableIndex + 1 + entriesToEvict, dynamicTableHeaderCount); nextDynamicTableIndex += entriesToEvict; } return entriesToEvict; } private void insertIntoDynamicTable(io.grpc.okhttp.internal.framed.Header entry) { int delta = entry.hpackSize; // if the new or replacement header is too big, drop all entries. if (delta > maxDynamicTableByteCount) { clearDynamicTable(); return; } // Evict headers to the required length. int bytesToRecover = dynamicTableByteCount + delta - maxDynamicTableByteCount; evictToRecoverBytes(bytesToRecover); if (dynamicTableHeaderCount + 1 > dynamicTable.length) { // Need to grow the dynamic table. io.grpc.okhttp.internal.framed.Header[] doubled = new io.grpc.okhttp.internal.framed.Header[dynamicTable.length * 2]; System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length); nextDynamicTableIndex = dynamicTable.length - 1; dynamicTable = doubled; } int index = nextDynamicTableIndex--; dynamicTable[index] = entry; dynamicTableHeaderCount++; dynamicTableByteCount += delta; } void resizeHeaderTable(int headerTableSizeSetting) { this.headerTableSizeSetting = headerTableSizeSetting; int effectiveHeaderTableSize = Math.min(headerTableSizeSetting, SETTINGS_HEADER_TABLE_SIZE_LIMIT); if (maxDynamicTableByteCount == effectiveHeaderTableSize) { // No change. return; } if (effectiveHeaderTableSize < maxDynamicTableByteCount) { smallestHeaderTableSizeSetting = Math.min(smallestHeaderTableSizeSetting, effectiveHeaderTableSize); } emitDynamicTableSizeUpdate = true; maxDynamicTableByteCount = effectiveHeaderTableSize; adjustDynamicTableByteCount(); } private void adjustDynamicTableByteCount() { if (maxDynamicTableByteCount < dynamicTableByteCount) { if (maxDynamicTableByteCount == 0) { clearDynamicTable(); } else { evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount); } } } } /** * An HTTP/2 response cannot contain uppercase header characters and must * be treated as malformed. */ private static ByteString checkLowercase(ByteString name) throws IOException { for (int i = 0, length = name.size(); i < length; i++) { byte c = name.getByte(i); if (c >= 'A' && c <= 'Z') { throw new IOException("PROTOCOL_ERROR response malformed: mixed case name: " + name.utf8()); } } return name; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy