1 /*
   2  * Copyright (c) 2012, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test 4235519 8004212 8005394 8007298 8006295 8006315 8006530 8007379 8008925
  26  *       8014217 8025003 8026330 8028397 8129544 8165243
  27  * @summary tests java.util.Base64
  28  * @library /test/lib
  29  * @run main TestBase64
  30  * @key randomness
  31  */
  32 
  33 import java.io.ByteArrayInputStream;
  34 import java.io.ByteArrayOutputStream;
  35 import java.io.InputStream;
  36 import java.io.IOException;
  37 import java.io.OutputStream;
  38 import java.nio.ByteBuffer;
  39 import java.util.Arrays;
  40 import java.util.ArrayList;
  41 import java.util.Base64;
  42 import java.util.List;
  43 import java.util.Random;
  44 
  45 import jdk.test.lib.RandomFactory;
  46 
  47 public class TestBase64 {
  48 
  49     private static final Random rnd = RandomFactory.getRandom();
  50 
  51     public static void main(String args[]) throws Throwable {
  52         int numRuns  = 10;
  53         int numBytes = 200;
  54         if (args.length > 1) {
  55             numRuns  = Integer.parseInt(args[0]);
  56             numBytes = Integer.parseInt(args[1]);
  57         }
  58 
  59         test(Base64.getEncoder(), Base64.getDecoder(), numRuns, numBytes);
  60         test(Base64.getUrlEncoder(), Base64.getUrlDecoder(), numRuns, numBytes);
  61         test(Base64.getMimeEncoder(), Base64.getMimeDecoder(), numRuns, numBytes);
  62 
  63         byte[] nl_1 = new byte[] {'\n'};
  64         byte[] nl_2 = new byte[] {'\n', '\r'};
  65         byte[] nl_3 = new byte[] {'\n', '\r', '\n'};
  66         for (int i = 0; i < 10; i++) {
  67             int len = rnd.nextInt(200) + 4;
  68             test(Base64.getMimeEncoder(len, nl_1),
  69                  Base64.getMimeDecoder(),
  70                  numRuns, numBytes);
  71             test(Base64.getMimeEncoder(len, nl_2),
  72                  Base64.getMimeDecoder(),
  73                  numRuns, numBytes);
  74             test(Base64.getMimeEncoder(len, nl_3),
  75                  Base64.getMimeDecoder(),
  76                  numRuns, numBytes);
  77         }
  78 
  79         testNull(Base64.getEncoder());
  80         testNull(Base64.getUrlEncoder());
  81         testNull(Base64.getMimeEncoder());
  82         testNull(Base64.getMimeEncoder(10, new byte[]{'\n'}));
  83         testNull(Base64.getDecoder());
  84         testNull(Base64.getUrlDecoder());
  85         testNull(Base64.getMimeDecoder());
  86         checkNull(() -> Base64.getMimeEncoder(10, null));
  87 
  88         testIOE(Base64.getEncoder());
  89         testIOE(Base64.getUrlEncoder());
  90         testIOE(Base64.getMimeEncoder());
  91         testIOE(Base64.getMimeEncoder(10, new byte[]{'\n'}));
  92 
  93         byte[] src = new byte[1024];
  94         rnd.nextBytes(src);
  95         final byte[] decoded = Base64.getEncoder().encode(src);
  96         testIOE(Base64.getDecoder(), decoded);
  97         testIOE(Base64.getMimeDecoder(), decoded);
  98         testIOE(Base64.getUrlDecoder(), Base64.getUrlEncoder().encode(src));
  99 
 100         // illegal line separator
 101         checkIAE(() -> Base64.getMimeEncoder(10, new byte[]{'\r', 'N'}));
 102 
 103         // malformed padding/ending
 104         testMalformedPadding();
 105 
 106         // illegal base64 character
 107         decoded[2] = (byte)0xe0;
 108         checkIAE(() -> Base64.getDecoder().decode(decoded));
 109         checkIAE(() -> Base64.getDecoder().decode(decoded, new byte[1024]));
 110         checkIAE(() -> Base64.getDecoder().decode(ByteBuffer.wrap(decoded)));
 111 
 112         // test single-non-base64 character for mime decoding
 113         testSingleNonBase64MimeDec();
 114 
 115         // test decoding of unpadded data
 116         testDecodeUnpadded();
 117 
 118         // test mime decoding with ignored character after padding
 119         testDecodeIgnoredAfterPadding();
 120 
 121         // given invalid args, encoder should not produce output
 122         testEncoderKeepsSilence(Base64.getEncoder());
 123         testEncoderKeepsSilence(Base64.getUrlEncoder());
 124         testEncoderKeepsSilence(Base64.getMimeEncoder());
 125 
 126         // given invalid args, decoder should not consume input
 127         testDecoderKeepsAbstinence(Base64.getDecoder());
 128         testDecoderKeepsAbstinence(Base64.getUrlDecoder());
 129         testDecoderKeepsAbstinence(Base64.getMimeDecoder());
 130     }
 131 
 132     private static void test(Base64.Encoder enc, Base64.Decoder dec,
 133                              int numRuns, int numBytes) throws Throwable {
 134         enc.encode(new byte[0]);
 135         dec.decode(new byte[0]);
 136 
 137         for (boolean withoutPadding : new boolean[] { false, true}) {
 138             if (withoutPadding) {
 139                  enc = enc.withoutPadding();
 140             }
 141             for (int i=0; i<numRuns; i++) {
 142                 for (int j=1; j<numBytes; j++) {
 143                     byte[] orig = new byte[j];
 144                     rnd.nextBytes(orig);
 145 
 146                     // --------testing encode/decode(byte[])--------
 147                     byte[] encoded = enc.encode(orig);
 148                     byte[] decoded = dec.decode(encoded);
 149 
 150                     checkEqual(orig, decoded,
 151                                "Base64 array encoding/decoding failed!");
 152                     if (withoutPadding) {
 153                         if (encoded[encoded.length - 1] == '=')
 154                             throw new RuntimeException(
 155                                "Base64 enc.encode().withoutPadding() has padding!");
 156                     }
 157                     // --------testing encodetoString(byte[])/decode(String)--------
 158                     String str = enc.encodeToString(orig);
 159                     if (!Arrays.equals(str.getBytes("ASCII"), encoded)) {
 160                         throw new RuntimeException(
 161                             "Base64 encodingToString() failed!");
 162                     }
 163                     byte[] buf = dec.decode(new String(encoded, "ASCII"));
 164                     checkEqual(buf, orig, "Base64 decoding(String) failed!");
 165 
 166                     //-------- testing encode/decode(Buffer)--------
 167                     testEncode(enc, ByteBuffer.wrap(orig), encoded);
 168                     ByteBuffer bin = ByteBuffer.allocateDirect(orig.length);
 169                     bin.put(orig).flip();
 170                     testEncode(enc, bin, encoded);
 171 
 172                     testDecode(dec, ByteBuffer.wrap(encoded), orig);
 173                     bin = ByteBuffer.allocateDirect(encoded.length);
 174                     bin.put(encoded).flip();
 175                     testDecode(dec, bin, orig);
 176 
 177                     // --------testing decode.wrap(input stream)--------
 178                     // 1) random buf length
 179                     ByteArrayInputStream bais = new ByteArrayInputStream(encoded);
 180                     InputStream is = dec.wrap(bais);
 181                     buf = new byte[orig.length + 10];
 182                     int len = orig.length;
 183                     int off = 0;
 184                     while (true) {
 185                         int n = rnd.nextInt(len);
 186                         if (n == 0)
 187                             n = 1;
 188                         n = is.read(buf, off, n);
 189                         if (n == -1) {
 190                             checkEqual(off, orig.length,
 191                                        "Base64 stream decoding failed");
 192                             break;
 193                         }
 194                         off += n;
 195                         len -= n;
 196                         if (len == 0)
 197                             break;
 198                     }
 199                     buf = Arrays.copyOf(buf, off);
 200                     checkEqual(buf, orig, "Base64 stream decoding failed!");
 201 
 202                     // 2) read one byte each
 203                     bais.reset();
 204                     is = dec.wrap(bais);
 205                     buf = new byte[orig.length + 10];
 206                     off = 0;
 207                     int b;
 208                     while ((b = is.read()) != -1) {
 209                         buf[off++] = (byte)b;
 210                     }
 211                     buf = Arrays.copyOf(buf, off);
 212                     checkEqual(buf, orig, "Base64 stream decoding failed!");
 213 
 214                     // --------testing encode.wrap(output stream)--------
 215                     ByteArrayOutputStream baos = new ByteArrayOutputStream((orig.length + 2) / 3 * 4 + 10);
 216                     OutputStream os = enc.wrap(baos);
 217                     off = 0;
 218                     len = orig.length;
 219                     for (int k = 0; k < 5; k++) {
 220                         if (len == 0)
 221                             break;
 222                         int n = rnd.nextInt(len);
 223                         if (n == 0)
 224                             n = 1;
 225                         os.write(orig, off, n);
 226                         off += n;
 227                         len -= n;
 228                     }
 229                     if (len != 0)
 230                         os.write(orig, off, len);
 231                     os.close();
 232                     buf = baos.toByteArray();
 233                     checkEqual(buf, encoded, "Base64 stream encoding failed!");
 234 
 235                     // 2) write one byte each
 236                     baos.reset();
 237                     os = enc.wrap(baos);
 238                     off = 0;
 239                     while (off < orig.length) {
 240                         os.write(orig[off++]);
 241                     }
 242                     os.close();
 243                     buf = baos.toByteArray();
 244                     checkEqual(buf, encoded, "Base64 stream encoding failed!");
 245 
 246                     // --------testing encode(in, out); -> bigger buf--------
 247                     buf = new byte[encoded.length + rnd.nextInt(100)];
 248                     int ret = enc.encode(orig, buf);
 249                     checkEqual(ret, encoded.length,
 250                                "Base64 enc.encode(src, null) returns wrong size!");
 251                     buf = Arrays.copyOf(buf, ret);
 252                     checkEqual(buf, encoded,
 253                                "Base64 enc.encode(src, dst) failed!");
 254 
 255                     // --------testing decode(in, out); -> bigger buf--------
 256                     buf = new byte[orig.length + rnd.nextInt(100)];
 257                     ret = dec.decode(encoded, buf);
 258                     checkEqual(ret, orig.length,
 259                               "Base64 enc.encode(src, null) returns wrong size!");
 260                     buf = Arrays.copyOf(buf, ret);
 261                     checkEqual(buf, orig,
 262                                "Base64 dec.decode(src, dst) failed!");
 263 
 264                 }
 265             }
 266         }
 267     }
 268 
 269     private static final byte[] ba_null = null;
 270     private static final String str_null = null;
 271     private static final ByteBuffer bb_null = null;
 272 
 273     private static void testNull(Base64.Encoder enc) {
 274         checkNull(() -> enc.encode(ba_null));
 275         checkNull(() -> enc.encodeToString(ba_null));
 276         checkNull(() -> enc.encode(ba_null, new byte[10]));
 277         checkNull(() -> enc.encode(new byte[10], ba_null));
 278         checkNull(() -> enc.encode(bb_null));
 279         checkNull(() -> enc.wrap((OutputStream)null));
 280     }
 281 
 282     private static void testNull(Base64.Decoder dec) {
 283         checkNull(() -> dec.decode(ba_null));
 284         checkNull(() -> dec.decode(str_null));
 285         checkNull(() -> dec.decode(ba_null, new byte[10]));
 286         checkNull(() -> dec.decode(new byte[10], ba_null));
 287         checkNull(() -> dec.decode(bb_null));
 288         checkNull(() -> dec.wrap((InputStream)null));
 289     }
 290 
 291     @FunctionalInterface
 292     private static interface Testable {
 293         public void test() throws Throwable;
 294     }
 295 
 296     private static void testIOE(Base64.Encoder enc) throws Throwable {
 297         ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
 298         OutputStream os = enc.wrap(baos);
 299         os.write(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9});
 300         os.close();
 301         checkIOE(() -> os.write(10));
 302         checkIOE(() -> os.write(new byte[] {10}));
 303         checkIOE(() -> os.write(new byte[] {10}, 1, 4));
 304     }
 305 
 306     private static void testIOE(Base64.Decoder dec, byte[] decoded) throws Throwable {
 307         ByteArrayInputStream bais = new ByteArrayInputStream(decoded);
 308         InputStream is = dec.wrap(bais);
 309         is.read(new byte[10]);
 310         is.close();
 311         checkIOE(() -> is.read());
 312         checkIOE(() -> is.read(new byte[] {10}));
 313         checkIOE(() -> is.read(new byte[] {10}, 1, 4));
 314         checkIOE(() -> is.available());
 315         checkIOE(() -> is.skip(20));
 316     }
 317 
 318     private static final void checkNull(Runnable r) {
 319         try {
 320             r.run();
 321             throw new RuntimeException("NPE is not thrown as expected");
 322         } catch (NullPointerException npe) {}
 323     }
 324 
 325     private static final void checkIOE(Testable t) throws Throwable {
 326         try {
 327             t.test();
 328             throw new RuntimeException("IOE is not thrown as expected");
 329         } catch (IOException ioe) {}
 330     }
 331 
 332     private static final void checkIAE(Runnable r) throws Throwable {
 333         try {
 334             r.run();
 335             throw new RuntimeException("IAE is not thrown as expected");
 336         } catch (IllegalArgumentException iae) {}
 337     }
 338 
 339     private static void testDecodeIgnoredAfterPadding() throws Throwable {
 340         for (byte nonBase64 : new byte[] {'#', '(', '!', '\\', '-', '_', '\n', '\r'}) {
 341             byte[][] src = new byte[][] {
 342                 "A".getBytes("ascii"),
 343                 "AB".getBytes("ascii"),
 344                 "ABC".getBytes("ascii"),
 345                 "ABCD".getBytes("ascii"),
 346                 "ABCDE".getBytes("ascii")
 347             };
 348             Base64.Encoder encM = Base64.getMimeEncoder();
 349             Base64.Decoder decM = Base64.getMimeDecoder();
 350             Base64.Encoder enc = Base64.getEncoder();
 351             Base64.Decoder dec = Base64.getDecoder();
 352             for (int i = 0; i < src.length; i++) {
 353                 // decode(byte[])
 354                 byte[] encoded = encM.encode(src[i]);
 355                 encoded = Arrays.copyOf(encoded, encoded.length + 1);
 356                 encoded[encoded.length - 1] = nonBase64;
 357                 checkEqual(decM.decode(encoded), src[i], "Non-base64 char is not ignored");
 358                 byte[] decoded = new byte[src[i].length];
 359                 decM.decode(encoded, decoded);
 360                 checkEqual(decoded, src[i], "Non-base64 char is not ignored");
 361 
 362                 try {
 363                     dec.decode(encoded);
 364                     throw new RuntimeException("No IAE for non-base64 char");
 365                 } catch (IllegalArgumentException iae) {}
 366             }
 367         }
 368     }
 369 
 370     private static void testMalformedPadding() throws Throwable {
 371         Object[] data = new Object[] {
 372             "$=#",       "",      0,      // illegal ending unit
 373             "A",         "",      0,      // dangling single byte
 374             "A=",        "",      0,
 375             "A==",       "",      0,
 376             "QUJDA",     "ABC",   4,
 377             "QUJDA=",    "ABC",   4,
 378             "QUJDA==",   "ABC",   4,
 379 
 380             "=",         "",      0,      // unnecessary padding
 381             "QUJD=",     "ABC",   4,      //"ABC".encode() -> "QUJD"
 382 
 383             "AA=",       "",      0,      // incomplete padding
 384             "QQ=",       "",      0,
 385             "QQ=N",      "",      0,      // incorrect padding
 386             "QQ=?",      "",      0,
 387             "QUJDQQ=",   "ABC",   4,
 388             "QUJDQQ=N",  "ABC",   4,
 389             "QUJDQQ=?",  "ABC",   4,
 390         };
 391 
 392         Base64.Decoder[] decs = new Base64.Decoder[] {
 393             Base64.getDecoder(),
 394             Base64.getUrlDecoder(),
 395             Base64.getMimeDecoder()
 396         };
 397 
 398         for (Base64.Decoder dec : decs) {
 399             for (int i = 0; i < data.length; i += 3) {
 400                 final String srcStr = (String)data[i];
 401                 final byte[] srcBytes = srcStr.getBytes("ASCII");
 402                 final ByteBuffer srcBB = ByteBuffer.wrap(srcBytes);
 403                 byte[] expected = ((String)data[i + 1]).getBytes("ASCII");
 404                 int pos = (Integer)data[i + 2];
 405 
 406                 // decode(byte[])
 407                 checkIAE(() -> dec.decode(srcBytes));
 408 
 409                 // decode(String)
 410                 checkIAE(() -> dec.decode(srcStr));
 411 
 412                 // decode(ByteBuffer)
 413                 checkIAE(() -> dec.decode(srcBB));
 414 
 415                 // wrap stream
 416                 checkIOE(new Testable() {
 417                     public void test() throws IOException {
 418                         try (InputStream is = dec.wrap(new ByteArrayInputStream(srcBytes))) {
 419                             while (is.read() != -1);
 420                         }
 421                 }});
 422             }
 423         }
 424 
 425         // anything left after padding is "invalid"/IAE, if
 426         // not MIME. In case of MIME, non-base64 character(s)
 427         // is ignored.
 428         checkIAE(() -> Base64.getDecoder().decode("AA==\u00D2"));
 429         checkIAE(() -> Base64.getUrlDecoder().decode("AA==\u00D2"));
 430         Base64.getMimeDecoder().decode("AA==\u00D2");
 431      }
 432 
 433     private static void  testDecodeUnpadded() throws Throwable {
 434         byte[] srcA = new byte[] { 'Q', 'Q' };
 435         byte[] srcAA = new byte[] { 'Q', 'Q', 'E'};
 436         Base64.Decoder dec = Base64.getDecoder();
 437         byte[] ret = dec.decode(srcA);
 438         if (ret[0] != 'A')
 439             throw new RuntimeException("Decoding unpadding input A failed");
 440         ret = dec.decode(srcAA);
 441         if (ret[0] != 'A' && ret[1] != 'A')
 442             throw new RuntimeException("Decoding unpadding input AA failed");
 443         ret = new byte[10];
 444         if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 1 &&
 445             ret[0] != 'A')
 446             throw new RuntimeException("Decoding unpadding input A from stream failed");
 447         if (dec.wrap(new ByteArrayInputStream(srcA)).read(ret) != 2 &&
 448             ret[0] != 'A' && ret[1] != 'A')
 449             throw new RuntimeException("Decoding unpadding input AA from stream failed");
 450     }
 451 
 452     // single-non-base64-char should be ignored for mime decoding, but
 453     // iae for basic decoding
 454     private static void testSingleNonBase64MimeDec() throws Throwable {
 455         for (String nonBase64 : new String[] {"#", "(", "!", "\\", "-", "_"}) {
 456             if (Base64.getMimeDecoder().decode(nonBase64).length != 0) {
 457                 throw new RuntimeException("non-base64 char is not ignored");
 458             }
 459             try {
 460                 Base64.getDecoder().decode(nonBase64);
 461                 throw new RuntimeException("No IAE for single non-base64 char");
 462             } catch (IllegalArgumentException iae) {}
 463         }
 464     }
 465 
 466     private static final void testEncode(Base64.Encoder enc, ByteBuffer bin, byte[] expected)
 467         throws Throwable {
 468 
 469         ByteBuffer bout = enc.encode(bin);
 470         byte[] buf = new byte[bout.remaining()];
 471         bout.get(buf);
 472         if (bin.hasRemaining()) {
 473             throw new RuntimeException(
 474                 "Base64 enc.encode(ByteBuffer) failed!");
 475         }
 476         checkEqual(buf, expected, "Base64 enc.encode(bf, bf) failed!");
 477     }
 478 
 479     private static final void testDecode(Base64.Decoder dec, ByteBuffer bin, byte[] expected)
 480         throws Throwable {
 481 
 482         ByteBuffer bout = dec.decode(bin);
 483         byte[] buf = new byte[bout.remaining()];
 484         bout.get(buf);
 485         checkEqual(buf, expected, "Base64 dec.decode(bf) failed!");
 486     }
 487 
 488     private static final void checkEqual(int v1, int v2, String msg)
 489         throws Throwable {
 490        if (v1 != v2) {
 491            System.out.printf("    v1=%d%n", v1);
 492            System.out.printf("    v2=%d%n", v2);
 493            throw new RuntimeException(msg);
 494        }
 495     }
 496 
 497     private static final void checkEqual(byte[] r1, byte[] r2, String msg)
 498         throws Throwable {
 499        if (!Arrays.equals(r1, r2)) {
 500            System.out.printf("    r1[%d]=[%s]%n", r1.length, new String(r1));
 501            System.out.printf("    r2[%d]=[%s]%n", r2.length, new String(r2));
 502            throw new RuntimeException(msg);
 503        }
 504     }
 505 
 506     // remove line feeds,
 507     private static final byte[] normalize(byte[] src) {
 508         int n = 0;
 509         boolean hasUrl = false;
 510         for (int i = 0; i < src.length; i++) {
 511             if (src[i] == '\r' || src[i] == '\n')
 512                 n++;
 513             if (src[i] == '-' || src[i] == '_')
 514                 hasUrl = true;
 515         }
 516         if (n == 0 && hasUrl == false)
 517             return src;
 518         byte[] ret = new byte[src.length - n];
 519         int j = 0;
 520         for (int i = 0; i < src.length; i++) {
 521             if (src[i] == '-')
 522                 ret[j++] = '+';
 523             else if (src[i] == '_')
 524                 ret[j++] = '/';
 525             else if (src[i] != '\r' && src[i] != '\n')
 526                 ret[j++] = src[i];
 527         }
 528         return ret;
 529     }
 530 
 531     private static void testEncoderKeepsSilence(Base64.Encoder enc)
 532             throws Throwable {
 533         List<Integer> vals = new ArrayList<>(List.of(Integer.MIN_VALUE,
 534                 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111,
 535                 Integer.MAX_VALUE - 1, Integer.MAX_VALUE));
 536         vals.addAll(List.of(rnd.nextInt(), rnd.nextInt(), rnd.nextInt(),
 537                 rnd.nextInt()));
 538         byte[] buf = new byte[] {1, 0, 91};
 539         for (int off : vals) {
 540             for (int len : vals) {
 541                 if (off >= 0 && len >= 0 && off <= buf.length - len) {
 542                     // valid args, skip them
 543                     continue;
 544                 }
 545                 // invalid args, test them
 546                 System.out.println("testing off=" + off + ", len=" + len);
 547 
 548                 ByteArrayOutputStream baos = new ByteArrayOutputStream(100);
 549                 try (OutputStream os = enc.wrap(baos)) {
 550                     os.write(buf, off, len);
 551                     throw new RuntimeException("Expected IOOBEx was not thrown");
 552                 } catch (IndexOutOfBoundsException expected) {
 553                 }
 554                 if (baos.size() > 0)
 555                     throw new RuntimeException("No output was expected, but got "
 556                             + baos.size() + " bytes");
 557             }
 558         }
 559     }
 560 
 561     private static void testDecoderKeepsAbstinence(Base64.Decoder dec)
 562             throws Throwable {
 563         List<Integer> vals = new ArrayList<>(List.of(Integer.MIN_VALUE,
 564                 Integer.MIN_VALUE + 1, -1111, -2, -1, 0, 1, 2, 3, 1111,
 565                 Integer.MAX_VALUE - 1, Integer.MAX_VALUE));
 566         vals.addAll(List.of(rnd.nextInt(), rnd.nextInt(), rnd.nextInt(),
 567                 rnd.nextInt()));
 568         byte[] buf = new byte[3];
 569         for (int off : vals) {
 570             for (int len : vals) {
 571                 if (off >= 0 && len >= 0 && off <= buf.length - len) {
 572                     // valid args, skip them
 573                     continue;
 574                 }
 575                 // invalid args, test them
 576                 System.out.println("testing off=" + off + ", len=" + len);
 577 
 578                 String input = "AAAAAAAAAAAAAAAAAAAAAA";
 579                 ByteArrayInputStream bais =
 580                         new ByteArrayInputStream(input.getBytes("Latin1"));
 581                 try (InputStream is = dec.wrap(bais)) {
 582                     is.read(buf, off, len);
 583                     throw new RuntimeException("Expected IOOBEx was not thrown");
 584                 } catch (IndexOutOfBoundsException expected) {
 585                 }
 586                 if (bais.available() != input.length())
 587                     throw new RuntimeException("No input should be consumed, "
 588                             + "but consumed " + (input.length() - bais.available())
 589                             + " bytes");
 590             }
 591         }
 592     }
 593 }