1 /*
   2  * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.incubator.http.internal.common;
  27 
  28 import jdk.incubator.http.HttpHeaders;
  29 import sun.net.NetProperties;
  30 import sun.net.util.IPAddressUtil;
  31 
  32 import javax.net.ssl.SSLParameters;
  33 import java.io.ByteArrayOutputStream;
  34 import java.io.Closeable;
  35 import java.io.IOException;
  36 import java.io.PrintStream;
  37 import java.io.UncheckedIOException;
  38 import java.io.UnsupportedEncodingException;
  39 import java.lang.System.Logger;
  40 import java.lang.System.Logger.Level;
  41 import java.net.InetSocketAddress;
  42 import java.net.URI;
  43 import java.net.URLPermission;
  44 import java.nio.ByteBuffer;
  45 import java.nio.charset.Charset;
  46 import java.nio.charset.StandardCharsets;
  47 import java.security.AccessController;
  48 import java.security.PrivilegedAction;
  49 import java.util.Arrays;
  50 import java.util.Collection;
  51 import java.util.List;
  52 import java.util.Set;
  53 import java.util.concurrent.CompletionException;
  54 import java.util.concurrent.ExecutionException;
  55 import java.util.function.Predicate;
  56 import java.util.function.Supplier;
  57 import java.util.stream.Stream;
  58 
  59 import static java.util.stream.Collectors.joining;
  60 
  61 /**
  62  * Miscellaneous utilities
  63  */
  64 public final class Utils {
  65 
  66     public static final boolean ASSERTIONSENABLED;
  67     static {
  68         boolean enabled = false;
  69         assert enabled = true;
  70         ASSERTIONSENABLED = enabled;
  71     }
  72 //    public static final boolean TESTING;
  73 //    static {
  74 //        if (ASSERTIONSENABLED) {
  75 //            PrivilegedAction<String> action = () -> System.getProperty("test.src");
  76 //            TESTING = AccessController.doPrivileged(action) != null;
  77 //        } else TESTING = false;
  78 //    }
  79     public static final boolean DEBUG = // Revisit: temporary dev flag.
  80             getBooleanProperty(DebugLogger.HTTP_NAME, false);
  81     public static final boolean DEBUG_HPACK = // Revisit: temporary dev flag.
  82             getBooleanProperty(DebugLogger.HPACK_NAME, false);
  83     public static final boolean TESTING = DEBUG;
  84 
  85     /**
  86      * Allocated buffer size. Must never be higher than 16K. But can be lower
  87      * if smaller allocation units preferred. HTTP/2 mandates that all
  88      * implementations support frame payloads of at least 16K.
  89      */
  90     private static final int DEFAULT_BUFSIZE = 16 * 1024;
  91 
  92     public static final int BUFSIZE = getIntegerNetProperty(
  93             "jdk.httpclient.bufsize", DEFAULT_BUFSIZE
  94     );
  95 
  96     private static final Set<String> DISALLOWED_HEADERS_SET = Set.of(
  97             "authorization", "connection", "cookie", "content-length",
  98             "date", "expect", "from", "host", "origin", "proxy-authorization",
  99             "referer", "user-agent", "upgrade", "via", "warning");
 100 
 101     public static final Predicate<String>
 102         ALLOWED_HEADERS = header -> !Utils.DISALLOWED_HEADERS_SET.contains(header);
 103 
 104     public static ByteBuffer getBuffer() {
 105         return ByteBuffer.allocate(BUFSIZE);
 106     }
 107 
 108     public static Throwable getCompletionCause(Throwable x) {
 109         if (!(x instanceof CompletionException)
 110                 && !(x instanceof ExecutionException)) return x;
 111         final Throwable cause = x.getCause();
 112         return cause == null ? x : cause;
 113     }
 114 
 115     public static IOException getIOException(Throwable t) {
 116         if (t instanceof IOException) {
 117             return (IOException) t;
 118         }
 119         Throwable cause = t.getCause();
 120         if (cause != null) {
 121             return getIOException(cause);
 122         }
 123         return new IOException(t);
 124     }
 125 
 126     private Utils() { }
 127 
 128     /**
 129      * Returns the security permissions required to connect to the proxy, or
 130      * {@code null} if none is required or applicable.
 131      */
 132     public static URLPermission permissionForProxy(InetSocketAddress proxyAddress) {
 133         if (proxyAddress == null)
 134             return null;
 135 
 136         StringBuilder sb = new StringBuilder();
 137         sb.append("socket://")
 138           .append(proxyAddress.getHostString()).append(":")
 139           .append(proxyAddress.getPort());
 140         String urlString = sb.toString();
 141         return new URLPermission(urlString, "CONNECT");
 142     }
 143 
 144     /**
 145      * Returns the security permission required for the given details.
 146      */
 147     public static URLPermission permissionForServer(URI uri,
 148                                                     String method,
 149                                                     Stream<String> headers) {
 150         String urlString = new StringBuilder()
 151                 .append(uri.getScheme()).append("://")
 152                 .append(uri.getAuthority())
 153                 .append(uri.getPath()).toString();
 154 
 155         StringBuilder actionStringBuilder = new StringBuilder(method);
 156         String collected = headers.collect(joining(","));
 157         if (!collected.isEmpty()) {
 158             actionStringBuilder.append(":").append(collected);
 159         }
 160         return new URLPermission(urlString, actionStringBuilder.toString());
 161     }
 162 
 163 
 164     // ABNF primitives defined in RFC 7230
 165     private static final boolean[] tchar      = new boolean[256];
 166     private static final boolean[] fieldvchar = new boolean[256];
 167 
 168     static {
 169         char[] allowedTokenChars =
 170                 ("!#$%&'*+-.^_`|~0123456789" +
 171                  "abcdefghijklmnopqrstuvwxyz" +
 172                  "ABCDEFGHIJKLMNOPQRSTUVWXYZ").toCharArray();
 173         for (char c : allowedTokenChars) {
 174             tchar[c] = true;
 175         }
 176         for (char c = 0x21; c < 0xFF; c++) {
 177             fieldvchar[c] = true;
 178         }
 179         fieldvchar[0x7F] = false; // a little hole (DEL) in the range
 180     }
 181 
 182     /*
 183      * Validates a RFC 7230 field-name.
 184      */
 185     public static boolean isValidName(String token) {
 186         for (int i = 0; i < token.length(); i++) {
 187             char c = token.charAt(i);
 188             if (c > 255 || !tchar[c]) {
 189                 return false;
 190             }
 191         }
 192         return !token.isEmpty();
 193     }
 194 
 195     /**
 196      * If the address was created with a domain name, then return
 197      * the domain name string. If created with a literal IP address
 198      * then return null. We do this to avoid doing a reverse lookup
 199      * Used to populate the TLS SNI parameter. So, SNI is only set
 200      * when a domain name was supplied.
 201      */
 202     public static String getServerName(InetSocketAddress addr) {
 203         String host = addr.getHostString();
 204         if (IPAddressUtil.textToNumericFormatV4(host) != null)
 205             return null;
 206         if (IPAddressUtil.textToNumericFormatV6(host) != null)
 207             return null;
 208         return host;
 209     }
 210 
 211     /*
 212      * Validates a RFC 7230 field-value.
 213      *
 214      * "Obsolete line folding" rule
 215      *
 216      *     obs-fold = CRLF 1*( SP / HTAB )
 217      *
 218      * is not permitted!
 219      */
 220     public static boolean isValidValue(String token) {
 221         boolean accepted = true;
 222         for (int i = 0; i < token.length(); i++) {
 223             char c = token.charAt(i);
 224             if (c > 255) {
 225                 return false;
 226             }
 227             if (accepted) {
 228                 if (c == ' ' || c == '\t') {
 229                     accepted = false;
 230                 } else if (!fieldvchar[c]) {
 231                     return false; // forbidden byte
 232                 }
 233             } else {
 234                 if (c != ' ' && c != '\t') {
 235                     if (fieldvchar[c]) {
 236                         accepted = true;
 237                     } else {
 238                         return false; // forbidden byte
 239                     }
 240                 }
 241             }
 242         }
 243         return accepted;
 244     }
 245 
 246     public static int getIntegerNetProperty(String name, int defaultValue) {
 247         return AccessController.doPrivileged((PrivilegedAction<Integer>) () ->
 248                 NetProperties.getInteger(name, defaultValue));
 249     }
 250 
 251     static String getNetProperty(String name) {
 252         return AccessController.doPrivileged((PrivilegedAction<String>) () ->
 253                 NetProperties.get(name));
 254     }
 255 
 256     static boolean getBooleanProperty(String name, boolean def) {
 257         return AccessController.doPrivileged((PrivilegedAction<Boolean>) () ->
 258                 Boolean.parseBoolean(System.getProperty(name, String.valueOf(def))));
 259     }
 260 
 261     public static SSLParameters copySSLParameters(SSLParameters p) {
 262         SSLParameters p1 = new SSLParameters();
 263         p1.setAlgorithmConstraints(p.getAlgorithmConstraints());
 264         p1.setCipherSuites(p.getCipherSuites());
 265         // JDK 8 EXCL START
 266         p1.setEnableRetransmissions(p.getEnableRetransmissions());
 267         p1.setMaximumPacketSize(p.getMaximumPacketSize());
 268         // JDK 8 EXCL END
 269         p1.setEndpointIdentificationAlgorithm(p.getEndpointIdentificationAlgorithm());
 270         p1.setNeedClientAuth(p.getNeedClientAuth());
 271         String[] protocols = p.getProtocols();
 272         if (protocols != null) {
 273             p1.setProtocols(protocols.clone());
 274         }
 275         p1.setSNIMatchers(p.getSNIMatchers());
 276         p1.setServerNames(p.getServerNames());
 277         p1.setUseCipherSuitesOrder(p.getUseCipherSuitesOrder());
 278         p1.setWantClientAuth(p.getWantClientAuth());
 279         return p1;
 280     }
 281 
 282     /**
 283      * Set limit to position, and position to mark.
 284      */
 285     public static void flipToMark(ByteBuffer buffer, int mark) {
 286         buffer.limit(buffer.position());
 287         buffer.position(mark);
 288     }
 289 
 290     public static String stackTrace(Throwable t) {
 291         ByteArrayOutputStream bos = new ByteArrayOutputStream();
 292         String s = null;
 293         try {
 294             PrintStream p = new PrintStream(bos, true, "US-ASCII");
 295             t.printStackTrace(p);
 296             s = bos.toString("US-ASCII");
 297         } catch (UnsupportedEncodingException ex) {
 298             throw new InternalError(ex); // Can't happen
 299         }
 300         return s;
 301     }
 302 
 303     /**
 304      * Copies as much of src to dst as possible.
 305      * Return number of bytes copied
 306      */
 307     public static int copy(ByteBuffer src, ByteBuffer dst) {
 308         int srcLen = src.remaining();
 309         int dstLen = dst.remaining();
 310         if (srcLen > dstLen) {
 311             int diff = srcLen - dstLen;
 312             int limit = src.limit();
 313             src.limit(limit - diff);
 314             dst.put(src);
 315             src.limit(limit);
 316         } else {
 317             dst.put(src);
 318         }
 319         return srcLen - src.remaining();
 320     }
 321 
 322     /** Threshold beyond which data is no longer copied into the current
 323      * buffer, if that buffer has enough unused space. */
 324     private static final int COPY_THRESHOLD = 8192;
 325 
 326     /**
 327      * Adds the data from buffersToAdd to currentList. Either 1) appends the
 328      * data from a particular buffer to the last buffer in the list ( if
 329      * there is enough unused space ), or 2) adds it to the list.
 330      *
 331      * @return the number of bytes added
 332      */
 333     public static long accumulateBuffers(List<ByteBuffer> currentList,
 334                                          List<ByteBuffer> buffersToAdd) {
 335         long accumulatedBytes = 0;
 336         for (ByteBuffer bufferToAdd : buffersToAdd) {
 337             int remaining = bufferToAdd.remaining();
 338             if (remaining <= 0)
 339                 continue;
 340             int listSize = currentList.size();
 341             if (listSize == 0) {
 342                 currentList.add(bufferToAdd);
 343                 accumulatedBytes = remaining;
 344                 continue;
 345             }
 346 
 347             ByteBuffer lastBuffer = currentList.get(listSize - 1);
 348             int freeSpace = lastBuffer.capacity() - lastBuffer.limit();
 349             if (remaining <= COPY_THRESHOLD && freeSpace >= remaining) {
 350                 // append the new data to the unused space in the last buffer
 351                 int position = lastBuffer.position();
 352                 int limit = lastBuffer.limit();
 353                 lastBuffer.position(limit);
 354                 lastBuffer.limit(limit + remaining);
 355                 lastBuffer.put(bufferToAdd);
 356                 lastBuffer.position(position);
 357             } else {
 358                 currentList.add(bufferToAdd);
 359             }
 360             accumulatedBytes += remaining;
 361         }
 362         return accumulatedBytes;
 363     }
 364 
 365     public static ByteBuffer copy(ByteBuffer src) {
 366         ByteBuffer dst = ByteBuffer.allocate(src.remaining());
 367         dst.put(src);
 368         dst.flip();
 369         return dst;
 370     }
 371 
 372     public static String dump(Object... objects) {
 373         return Arrays.toString(objects);
 374     }
 375 
 376     public static String stringOf(Collection<?> source) {
 377         // We don't know anything about toString implementation of this
 378         // collection, so let's create an array
 379         return Arrays.toString(source.toArray());
 380     }
 381 
 382     public static long remaining(ByteBuffer[] bufs) {
 383         long remain = 0;
 384         for (ByteBuffer buf : bufs) {
 385             remain += buf.remaining();
 386         }
 387         return remain;
 388     }
 389 
 390     public static boolean hasRemaining(List<ByteBuffer> bufs) {
 391         synchronized (bufs) {
 392             for (ByteBuffer buf : bufs) {
 393                 if (buf.hasRemaining())
 394                     return true;
 395             }
 396         }
 397         return false;
 398     }
 399 
 400     public static long remaining(List<ByteBuffer> bufs) {
 401         long remain = 0;
 402         synchronized (bufs) {
 403             for (ByteBuffer buf : bufs) {
 404                 remain += buf.remaining();
 405             }
 406         }
 407         return remain;
 408     }
 409 
 410     public static int remaining(List<ByteBuffer> bufs, int max) {
 411         long remain = 0;
 412         synchronized (bufs) {
 413             for (ByteBuffer buf : bufs) {
 414                 remain += buf.remaining();
 415                 if (remain > max) {
 416                     throw new IllegalArgumentException("too many bytes");
 417                 }
 418             }
 419         }
 420         return (int) remain;
 421     }
 422 
 423     public static long remaining(ByteBufferReference[] refs) {
 424         long remain = 0;
 425         for (ByteBufferReference ref : refs) {
 426             remain += ref.get().remaining();
 427         }
 428         return remain;
 429     }
 430 
 431     public static int remaining(ByteBufferReference[] refs, int max) {
 432         long remain = 0;
 433         for (ByteBufferReference ref : refs) {
 434             remain += ref.get().remaining();
 435             if (remain > max) {
 436                 throw new IllegalArgumentException("too many bytes");
 437             }
 438         }
 439         return (int) remain;
 440     }
 441 
 442     public static int remaining(ByteBuffer[] refs, int max) {
 443         long remain = 0;
 444         for (ByteBuffer b : refs) {
 445             remain += b.remaining();
 446             if (remain > max) {
 447                 throw new IllegalArgumentException("too many bytes");
 448             }
 449         }
 450         return (int) remain;
 451     }
 452 
 453     public static void close(Closeable... closeables) {
 454         for (Closeable c : closeables) {
 455             try {
 456                 c.close();
 457             } catch (IOException ignored) { }
 458         }
 459     }
 460 
 461     // Put all these static 'empty' singletons here
 462     public static final ByteBuffer EMPTY_BYTEBUFFER = ByteBuffer.allocate(0);
 463     public static final ByteBuffer[] EMPTY_BB_ARRAY = new ByteBuffer[0];
 464     public static final List<ByteBuffer> EMPTY_BB_LIST = List.of();
 465     public static final ByteBufferReference[] EMPTY_BBR_ARRAY = new ByteBufferReference[0];
 466 
 467     public static ByteBuffer slice(ByteBuffer buffer, int amount) {
 468         ByteBuffer newb = buffer.slice();
 469         newb.limit(amount);
 470         buffer.position(buffer.position() + amount);
 471         return newb;
 472     }
 473 
 474     /**
 475      * Get the Charset from the Content-encoding header. Defaults to
 476      * UTF_8
 477      */
 478     public static Charset charsetFrom(HttpHeaders headers) {
 479         String encoding = headers.firstValue("Content-encoding")
 480                 .orElse("UTF_8");
 481         try {
 482             return Charset.forName(encoding);
 483         } catch (IllegalArgumentException e) {
 484             return StandardCharsets.UTF_8;
 485         }
 486     }
 487 
 488     public static UncheckedIOException unchecked(IOException e) {
 489         return new UncheckedIOException(e);
 490     }
 491 
 492     /**
 493      * Get a logger for debug HTTP traces.
 494      *
 495      * The logger should only be used with levels whose severity is
 496      * {@code <= DEBUG}. By default, this logger will forward all messages
 497      * logged to an internal logger named "jdk.internal.httpclient.debug".
 498      * In addition, if the property -Djdk.internal.httpclient.debug=true is set,
 499      * it will print the messages on stderr.
 500      * The logger will add some decoration to the printed message, in the form of
 501      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
 502      *
 503      * @param dbgTag A lambda that returns a string that identifies the caller
 504      *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
 505      *
 506      * @return A logger for HTTP internal debug traces
 507      */
 508     public static Logger getDebugLogger(Supplier<String> dbgTag) {
 509         return getDebugLogger(dbgTag, DEBUG);
 510     }
 511 
 512     /**
 513      * Get a logger for debug HTTP traces.The logger should only be used
 514      * with levels whose severity is {@code <= DEBUG}.
 515      *
 516      * By default, this logger will forward all messages logged to an internal
 517      * logger named "jdk.internal.httpclient.debug".
 518      * In addition, if the message severity level is >= to
 519      * the provided {@code errLevel} it will print the messages on stderr.
 520      * The logger will add some decoration to the printed message, in the form of
 521      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
 522      *
 523      * @apiNote To obtain a logger that will always print things on stderr in
 524      *          addition to forwarding to the internal logger, use
 525      *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
 526      *          This is also equivalent to calling
 527      *          {@code getDebugLogger(this::dbgTag, true);}.
 528      *          To obtain a logger that will only forward to the internal logger,
 529      *          use {@code getDebugLogger(this::dbgTag, Level.OFF);}.
 530      *          This is also equivalent to calling
 531      *          {@code getDebugLogger(this::dbgTag, false);}.
 532      *
 533      * @param dbgTag A lambda that returns a string that identifies the caller
 534      *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
 535      * @param errLevel The level above which messages will be also printed on
 536      *               stderr (in addition to be forwarded to the internal logger).
 537      *
 538      * @return A logger for HTTP internal debug traces
 539      */
 540     static Logger getDebugLogger(Supplier<String> dbgTag, Level errLevel) {
 541         return DebugLogger.createHttpLogger(dbgTag, Level.OFF, errLevel);
 542     }
 543 
 544     /**
 545      * Get a logger for debug HTTP traces.The logger should only be used
 546      * with levels whose severity is {@code <= DEBUG}.
 547      *
 548      * By default, this logger will forward all messages logged to an internal
 549      * logger named "jdk.internal.httpclient.debug".
 550      * In addition, the provided boolean {@code on==true}, it will print the
 551      * messages on stderr.
 552      * The logger will add some decoration to the printed message, in the form of
 553      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
 554      *
 555      * @apiNote To obtain a logger that will always print things on stderr in
 556      *          addition to forwarding to the internal logger, use
 557      *          {@code getDebugLogger(this::dbgTag, true);}.
 558      *          This is also equivalent to calling
 559      *          {@code getDebugLogger(this::dbgTag, Level.ALL);}.
 560      *          To obtain a logger that will only forward to the internal logger,
 561      *          use {@code getDebugLogger(this::dbgTag, false);}.
 562      *          This is also equivalent to calling
 563      *          {@code getDebugLogger(this::dbgTag, Level.OFF);}.
 564      *
 565      * @param dbgTag A lambda that returns a string that identifies the caller
 566      *               (e.g: "SocketTube(3)", or "Http2Connection(SocketTube(3))")
 567      * @param on  Whether messages should also be printed on
 568      *               stderr (in addition to be forwarded to the internal logger).
 569      *
 570      * @return A logger for HTTP internal debug traces
 571      */
 572     public static Logger getDebugLogger(Supplier<String> dbgTag, boolean on) {
 573         Level errLevel = on ? Level.ALL : Level.OFF;
 574         return getDebugLogger(dbgTag, errLevel);
 575     }
 576 
 577     /**
 578      * Get a logger for debug HPACK traces.The logger should only be used
 579      * with levels whose severity is {@code <= DEBUG}.
 580      *
 581      * By default, this logger will forward all messages logged to an internal
 582      * logger named "jdk.internal.httpclient.hpack.debug".
 583      * In addition, if the message severity level is >= to
 584      * the provided {@code outLevel} it will print the messages on stdout.
 585      * The logger will add some decoration to the printed message, in the form of
 586      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
 587      *
 588      * @apiNote To obtain a logger that will always print things on stdout in
 589      *          addition to forwarding to the internal logger, use
 590      *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
 591      *          This is also equivalent to calling
 592      *          {@code getHpackLogger(this::dbgTag, true);}.
 593      *          To obtain a logger that will only forward to the internal logger,
 594      *          use {@code getHpackLogger(this::dbgTag, Level.OFF);}.
 595      *          This is also equivalent to calling
 596      *          {@code getHpackLogger(this::dbgTag, false);}.
 597      *
 598      * @param dbgTag A lambda that returns a string that identifies the caller
 599      *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
 600      * @param outLevel The level above which messages will be also printed on
 601      *               stdout (in addition to be forwarded to the internal logger).
 602      *
 603      * @return A logger for HPACK internal debug traces
 604      */
 605     public static Logger getHpackLogger(Supplier<String> dbgTag, Level outLevel) {
 606         Level errLevel = Level.OFF;
 607         return DebugLogger.createHpackLogger(dbgTag, outLevel, errLevel);
 608     }
 609 
 610     /**
 611      * Get a logger for debug HPACK traces.The logger should only be used
 612      * with levels whose severity is {@code <= DEBUG}.
 613      *
 614      * By default, this logger will forward all messages logged to an internal
 615      * logger named "jdk.internal.httpclient.hpack.debug".
 616      * In addition, the provided boolean {@code on==true}, it will print the
 617      * messages on stdout.
 618      * The logger will add some decoration to the printed message, in the form of
 619      * {@code <Level>:[<thread-name>] [<elapsed-time>] <dbgTag>: <formatted message>}
 620      *
 621      * @apiNote To obtain a logger that will always print things on stdout in
 622      *          addition to forwarding to the internal logger, use
 623      *          {@code getHpackLogger(this::dbgTag, true);}.
 624      *          This is also equivalent to calling
 625      *          {@code getHpackLogger(this::dbgTag, Level.ALL);}.
 626      *          To obtain a logger that will only forward to the internal logger,
 627      *          use {@code getHpackLogger(this::dbgTag, false);}.
 628      *          This is also equivalent to calling
 629      *          {@code getHpackLogger(this::dbgTag, Level.OFF);}.
 630      *
 631      * @param dbgTag A lambda that returns a string that identifies the caller
 632      *               (e.g: "Http2Connection(SocketTube(3))/hpack.Decoder(3)")
 633      * @param on  Whether messages should also be printed on
 634      *            stdout (in addition to be forwarded to the internal logger).
 635      *
 636      * @return A logger for HPACK internal debug traces
 637      */
 638     public static Logger getHpackLogger(Supplier<String> dbgTag, boolean on) {
 639         Level outLevel = on ? Level.ALL : Level.OFF;
 640         return getHpackLogger(dbgTag, outLevel);
 641     }
 642 }