| /* |
| * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| package jdk.internal.net.http.hpack; |
| |
| import jdk.internal.net.http.common.Utils; |
| import jdk.internal.net.http.hpack.HPACK.Logger.Level; |
| |
| import java.nio.ByteBuffer; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.Map; |
| import java.util.ResourceBundle; |
| import java.util.function.Supplier; |
| |
| import static java.lang.String.format; |
| import static java.util.stream.Collectors.joining; |
| import static jdk.internal.net.http.hpack.HPACK.Logger.Level.EXTRA; |
| import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NONE; |
| import static jdk.internal.net.http.hpack.HPACK.Logger.Level.NORMAL; |
| |
| /** |
| * Internal utilities and stuff. |
| */ |
| public final class HPACK { |
| |
| private static final RootLogger LOGGER; |
| private static final Map<String, Level> logLevels = |
| Map.of("NORMAL", NORMAL, "EXTRA", EXTRA); |
| |
| static { |
| String PROPERTY = "jdk.internal.httpclient.hpack.log.level"; |
| |
| String value = AccessController.doPrivileged( |
| (PrivilegedAction<String>) () -> System.getProperty(PROPERTY)); |
| |
| if (value == null) { |
| LOGGER = new RootLogger(NONE); |
| } else { |
| String upperCasedValue = value.toUpperCase(); |
| Level l = logLevels.get(upperCasedValue); |
| if (l == null) { |
| LOGGER = new RootLogger(NONE); |
| LOGGER.log(System.Logger.Level.INFO, |
| () -> format("%s value '%s' not recognized (use %s); logging disabled", |
| PROPERTY, value, logLevels.keySet().stream().collect(joining(", ")))); |
| } else { |
| LOGGER = new RootLogger(l); |
| LOGGER.log(System.Logger.Level.DEBUG, |
| () -> format("logging level %s", l)); |
| } |
| } |
| } |
| |
| public static Logger getLogger() { |
| return LOGGER; |
| } |
| |
| private HPACK() { } |
| |
| /** |
| * The purpose of this logger is to provide means of diagnosing issues _in |
| * the HPACK implementation_. It's not a general purpose logger. |
| */ |
| // implements System.Logger to make it possible to skip this class |
| // when looking for the Caller. |
| public static class Logger implements System.Logger { |
| |
| /** |
| * Log detail level. |
| */ |
| public enum Level { |
| |
| NONE(0, System.Logger.Level.OFF), |
| NORMAL(1, System.Logger.Level.DEBUG), |
| EXTRA(2, System.Logger.Level.TRACE); |
| |
| private final int level; |
| final System.Logger.Level systemLevel; |
| |
| Level(int i, System.Logger.Level system) { |
| level = i; |
| systemLevel = system; |
| } |
| |
| public final boolean implies(Level other) { |
| return this.level >= other.level; |
| } |
| } |
| |
| private final String name; |
| private final Level level; |
| private final String path; |
| private final System.Logger logger; |
| |
| private Logger(String path, String name, Level level) { |
| this(path, name, level, null); |
| } |
| |
| private Logger(String p, String name, Level level, System.Logger logger) { |
| this.path = p; |
| this.name = name; |
| this.level = level; |
| this.logger = Utils.getHpackLogger(path::toString, level.systemLevel); |
| } |
| |
| public final String getName() { |
| return name; |
| } |
| |
| @Override |
| public boolean isLoggable(System.Logger.Level level) { |
| return logger.isLoggable(level); |
| } |
| |
| @Override |
| public void log(System.Logger.Level level, ResourceBundle bundle, String msg, Throwable thrown) { |
| logger.log(level, bundle, msg,thrown); |
| } |
| |
| @Override |
| public void log(System.Logger.Level level, ResourceBundle bundle, String format, Object... params) { |
| logger.log(level, bundle, format, params); |
| } |
| |
| /* |
| * Usual performance trick for logging, reducing performance overhead in |
| * the case where logging with the specified level is a NOP. |
| */ |
| |
| public boolean isLoggable(Level level) { |
| return this.level.implies(level); |
| } |
| |
| public void log(Level level, Supplier<String> s) { |
| if (this.level.implies(level)) { |
| logger.log(level.systemLevel, s); |
| } |
| } |
| |
| public Logger subLogger(String name) { |
| return new Logger(path + "/" + name, name, level); |
| } |
| |
| } |
| |
| private static final class RootLogger extends Logger { |
| |
| protected RootLogger(Level level) { |
| super("hpack", "hpack", level); |
| } |
| |
| } |
| |
| // -- low-level utilities -- |
| |
| @FunctionalInterface |
| interface BufferUpdateConsumer { |
| void accept(long data, int len); |
| } |
| |
| @SuppressWarnings("fallthrough") |
| public static int read(ByteBuffer source, |
| long buffer, |
| int bufferLen, |
| BufferUpdateConsumer consumer) |
| { |
| // read as much as possible (up to 8 bytes) |
| int nBytes = Math.min((64 - bufferLen) >> 3, source.remaining()); |
| switch (nBytes) { |
| case 0: |
| break; |
| case 3: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| case 2: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| case 1: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| consumer.accept(buffer, bufferLen); |
| break; |
| case 7: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| case 6: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| case 5: |
| buffer |= ((source.get() & 0x00000000000000ffL) << (56 - bufferLen)); |
| bufferLen += 8; |
| case 4: |
| buffer |= ((source.getInt() & 0x00000000ffffffffL) << (32 - bufferLen)); |
| bufferLen += 32; |
| consumer.accept(buffer, bufferLen); |
| break; |
| case 8: |
| buffer = source.getLong(); |
| bufferLen = 64; |
| consumer.accept(buffer, bufferLen); |
| break; |
| default: |
| throw new InternalError(String.valueOf(nBytes)); |
| } |
| return nBytes; |
| } |
| |
| // The number of bytes that can be written at once |
| // (calculating in bytes, not bits, since |
| // destination.remaining() * 8 might overflow) |
| @SuppressWarnings("fallthrough") |
| public static int write(long buffer, |
| int bufferLen, |
| BufferUpdateConsumer consumer, |
| ByteBuffer destination) |
| { |
| int nBytes = Math.min(bufferLen >> 3, destination.remaining()); |
| switch (nBytes) { |
| case 0: |
| break; |
| case 3: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| case 2: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| case 1: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| consumer.accept(buffer, bufferLen); |
| break; |
| case 7: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| case 6: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| case 5: |
| destination.put((byte) (buffer >>> 56)); |
| buffer <<= 8; |
| bufferLen -= 8; |
| case 4: |
| destination.putInt((int) (buffer >>> 32)); |
| buffer <<= 32; |
| bufferLen -= 32; |
| consumer.accept(buffer, bufferLen); |
| break; |
| case 8: |
| destination.putLong(buffer); |
| buffer = 0; |
| bufferLen = 0; |
| consumer.accept(buffer, bufferLen); |
| break; |
| default: |
| throw new InternalError(String.valueOf(nBytes)); |
| } |
| return nBytes; |
| } |
| |
| /* |
| * Returns the number of bytes the given number of bits constitute. |
| */ |
| static int bytesForBits(int n) { |
| assert (n / 8 + (n % 8 != 0 ? 1 : 0)) == (n + 7) / 8 |
| && (n + 7) / 8 == ((n + 7) >> 3) : n; |
| return (n + 7) >> 3; |
| } |
| } |