1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.util;
18 
19 import androidx.test.filters.LargeTest;
20 
21 import junit.framework.TestCase;
22 
23 import java.io.ByteArrayInputStream;
24 import java.io.ByteArrayOutputStream;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.OutputStream;
28 import java.util.Arrays;
29 import java.util.Collections;
30 import java.util.List;
31 import java.util.Random;
32 import java.util.stream.Collectors;
33 
34 @LargeTest
35 public class Base64Test extends TestCase {
36     private static final String TAG = "Base64Test";
37 
38     /** Decodes a string, returning a string. */
decodeString(String in)39     private String decodeString(String in) throws Exception {
40         byte[] out = Base64.decode(in, 0);
41         return new String(out);
42     }
43 
44     /**
45      * Encodes the string 'in' using 'flags'.  Asserts that decoding
46      * gives the same string.  Returns the encoded string.
47      */
encodeToString(String in, int flags)48     private String encodeToString(String in, int flags) throws Exception {
49         String b64 = Base64.encodeToString(in.getBytes(), flags);
50         String dec = decodeString(b64);
51         assertEquals(in, dec);
52         return b64;
53     }
54 
55     /** Assert that decoding 'in' throws IllegalArgumentException. */
assertBad(String in)56     private void assertBad(String in) throws Exception {
57         try {
58             byte[] out = Base64.decode(in, 0);
59             fail("should have failed to decode");
60         } catch (IllegalArgumentException e) {
61         }
62     }
63 
64     /** Assert that actual equals the first len bytes of expected. */
assertEquals(byte[] expected, int len, byte[] actual)65     private void assertEquals(byte[] expected, int len, byte[] actual) {
66         assertEquals(len, actual.length);
67         for (int i = 0; i < len; ++i) {
68             assertEquals(expected[i], actual[i]);
69         }
70     }
71 
72     /** Assert that actual equals the first len bytes of expected. */
assertEquals(byte[] expected, int len, byte[] actual, int alen)73     private void assertEquals(byte[] expected, int len, byte[] actual, int alen) {
74         assertEquals(len, alen);
75         for (int i = 0; i < len; ++i) {
76             assertEquals(expected[i], actual[i]);
77         }
78     }
79 
80     /** Assert that actual equals the first len bytes of expected. */
assertEquals(byte[] expected, byte[] actual)81     private void assertEquals(byte[] expected, byte[] actual) {
82         assertEquals(expected.length, actual.length);
83         for (int i = 0; i < expected.length; ++i) {
84             assertEquals(expected[i], actual[i]);
85         }
86     }
87 
testDecodeExtraChars()88     public void testDecodeExtraChars() throws Exception {
89         // padding 0
90         assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
91         assertBad("aGVsbG8sIHdvcmxk=");
92         assertBad("aGVsbG8sIHdvcmxk==");
93         assertBad("aGVsbG8sIHdvcmxk =");
94         assertBad("aGVsbG8sIHdvcmxk = = ");
95         assertEquals("hello, world", decodeString(" aGVs bG8s IHdv cmxk  "));
96         assertEquals("hello, world", decodeString(" aGV sbG8 sIHd vcmx k "));
97         assertEquals("hello, world", decodeString(" aG VsbG 8sIH dvcm xk "));
98         assertEquals("hello, world", decodeString(" a GVsb G8sI Hdvc mxk "));
99         assertEquals("hello, world", decodeString(" a G V s b G 8 s I H d v c m x k "));
100         assertEquals("hello, world", decodeString("_a*G_V*s_b*G_8*s_I*H_d*v_c*m_x*k_"));
101         assertEquals("hello, world", decodeString("aGVsbG8sIHdvcmxk"));
102 
103         // padding 1
104         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE="));
105         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPyE"));
106         assertBad("aGVsbG8sIHdvcmxkPyE==");
107         assertBad("aGVsbG8sIHdvcmxkPyE ==");
108         assertBad("aGVsbG8sIHdvcmxkPyE = = ");
109         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E="));
110         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E"));
111         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E ="));
112         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E "));
113         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E = "));
114         assertEquals("hello, world?!", decodeString("aGVsbG8sIHdvcmxkPy E   "));
115 
116         // padding 2
117         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg=="));
118         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkLg"));
119         assertBad("aGVsbG8sIHdvcmxkLg=");
120         assertBad("aGVsbG8sIHdvcmxkLg =");
121         assertBad("aGVsbG8sIHdvcmxkLg = ");
122         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g=="));
123         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g"));
124         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g =="));
125         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g "));
126         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g = = "));
127         assertEquals("hello, world.", decodeString("aGVsbG8sIHdvcmxkL g   "));
128     }
129 
130     private static final byte[] BYTES = { (byte) 0xff, (byte) 0xee, (byte) 0xdd,
131                                           (byte) 0xcc, (byte) 0xbb, (byte) 0xaa,
132                                           (byte) 0x99, (byte) 0x88, (byte) 0x77 };
133 
testBinaryDecode()134     public void testBinaryDecode() throws Exception {
135         assertEquals(BYTES, 0, Base64.decode("", 0));
136         assertEquals(BYTES, 1, Base64.decode("/w==", 0));
137         assertEquals(BYTES, 2, Base64.decode("/+4=", 0));
138         assertEquals(BYTES, 3, Base64.decode("/+7d", 0));
139         assertEquals(BYTES, 4, Base64.decode("/+7dzA==", 0));
140         assertEquals(BYTES, 5, Base64.decode("/+7dzLs=", 0));
141         assertEquals(BYTES, 6, Base64.decode("/+7dzLuq", 0));
142         assertEquals(BYTES, 7, Base64.decode("/+7dzLuqmQ==", 0));
143         assertEquals(BYTES, 8, Base64.decode("/+7dzLuqmYg=", 0));
144     }
145 
testWebSafe()146     public void testWebSafe() throws Exception {
147         assertEquals(BYTES, 0, Base64.decode("", Base64.URL_SAFE));
148         assertEquals(BYTES, 1, Base64.decode("_w==", Base64.URL_SAFE));
149         assertEquals(BYTES, 2, Base64.decode("_-4=", Base64.URL_SAFE));
150         assertEquals(BYTES, 3, Base64.decode("_-7d", Base64.URL_SAFE));
151         assertEquals(BYTES, 4, Base64.decode("_-7dzA==", Base64.URL_SAFE));
152         assertEquals(BYTES, 5, Base64.decode("_-7dzLs=", Base64.URL_SAFE));
153         assertEquals(BYTES, 6, Base64.decode("_-7dzLuq", Base64.URL_SAFE));
154         assertEquals(BYTES, 7, Base64.decode("_-7dzLuqmQ==", Base64.URL_SAFE));
155         assertEquals(BYTES, 8, Base64.decode("_-7dzLuqmYg=", Base64.URL_SAFE));
156 
157         assertEquals("", Base64.encodeToString(BYTES, 0, 0, Base64.URL_SAFE));
158         assertEquals("_w==\n", Base64.encodeToString(BYTES, 0, 1, Base64.URL_SAFE));
159         assertEquals("_-4=\n", Base64.encodeToString(BYTES, 0, 2, Base64.URL_SAFE));
160         assertEquals("_-7d\n", Base64.encodeToString(BYTES, 0, 3, Base64.URL_SAFE));
161         assertEquals("_-7dzA==\n", Base64.encodeToString(BYTES, 0, 4, Base64.URL_SAFE));
162         assertEquals("_-7dzLs=\n", Base64.encodeToString(BYTES, 0, 5, Base64.URL_SAFE));
163         assertEquals("_-7dzLuq\n", Base64.encodeToString(BYTES, 0, 6, Base64.URL_SAFE));
164         assertEquals("_-7dzLuqmQ==\n", Base64.encodeToString(BYTES, 0, 7, Base64.URL_SAFE));
165         assertEquals("_-7dzLuqmYg=\n", Base64.encodeToString(BYTES, 0, 8, Base64.URL_SAFE));
166     }
167 
testFlags()168     public void testFlags() throws Exception {
169         assertEquals("YQ==\n",       encodeToString("a", 0));
170         assertEquals("YQ==",         encodeToString("a", Base64.NO_WRAP));
171         assertEquals("YQ\n",         encodeToString("a", Base64.NO_PADDING));
172         assertEquals("YQ",           encodeToString("a", Base64.NO_PADDING | Base64.NO_WRAP));
173         assertEquals("YQ==\r\n",     encodeToString("a", Base64.CRLF));
174         assertEquals("YQ\r\n",       encodeToString("a", Base64.CRLF | Base64.NO_PADDING));
175 
176         assertEquals("YWI=\n",       encodeToString("ab", 0));
177         assertEquals("YWI=",         encodeToString("ab", Base64.NO_WRAP));
178         assertEquals("YWI\n",        encodeToString("ab", Base64.NO_PADDING));
179         assertEquals("YWI",          encodeToString("ab", Base64.NO_PADDING | Base64.NO_WRAP));
180         assertEquals("YWI=\r\n",     encodeToString("ab", Base64.CRLF));
181         assertEquals("YWI\r\n",      encodeToString("ab", Base64.CRLF | Base64.NO_PADDING));
182 
183         assertEquals("YWJj\n",       encodeToString("abc", 0));
184         assertEquals("YWJj",         encodeToString("abc", Base64.NO_WRAP));
185         assertEquals("YWJj\n",       encodeToString("abc", Base64.NO_PADDING));
186         assertEquals("YWJj",         encodeToString("abc", Base64.NO_PADDING | Base64.NO_WRAP));
187         assertEquals("YWJj\r\n",     encodeToString("abc", Base64.CRLF));
188         assertEquals("YWJj\r\n",     encodeToString("abc", Base64.CRLF | Base64.NO_PADDING));
189 
190         assertEquals("YWJjZA==\n",   encodeToString("abcd", 0));
191         assertEquals("YWJjZA==",     encodeToString("abcd", Base64.NO_WRAP));
192         assertEquals("YWJjZA\n",     encodeToString("abcd", Base64.NO_PADDING));
193         assertEquals("YWJjZA",       encodeToString("abcd", Base64.NO_PADDING | Base64.NO_WRAP));
194         assertEquals("YWJjZA==\r\n", encodeToString("abcd", Base64.CRLF));
195         assertEquals("YWJjZA\r\n",   encodeToString("abcd", Base64.CRLF | Base64.NO_PADDING));
196     }
197 
testLineLength()198     public void testLineLength() throws Exception {
199         String in_56 = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd";
200         String in_57 = in_56 + "e";
201         String in_58 = in_56 + "ef";
202         String in_59 = in_56 + "efg";
203         String in_60 = in_56 + "efgh";
204         String in_61 = in_56 + "efghi";
205 
206         String prefix = "YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5emFi";
207         String out_56 = prefix + "Y2Q=\n";
208         String out_57 = prefix + "Y2Rl\n";
209         String out_58 = prefix + "Y2Rl\nZg==\n";
210         String out_59 = prefix + "Y2Rl\nZmc=\n";
211         String out_60 = prefix + "Y2Rl\nZmdo\n";
212         String out_61 = prefix + "Y2Rl\nZmdoaQ==\n";
213 
214         // no newline for an empty input array.
215         assertEquals("", encodeToString("", 0));
216 
217         assertEquals(out_56, encodeToString(in_56, 0));
218         assertEquals(out_57, encodeToString(in_57, 0));
219         assertEquals(out_58, encodeToString(in_58, 0));
220         assertEquals(out_59, encodeToString(in_59, 0));
221         assertEquals(out_60, encodeToString(in_60, 0));
222         assertEquals(out_61, encodeToString(in_61, 0));
223 
224         assertEquals(out_56.replaceAll("=", ""), encodeToString(in_56, Base64.NO_PADDING));
225         assertEquals(out_57.replaceAll("=", ""), encodeToString(in_57, Base64.NO_PADDING));
226         assertEquals(out_58.replaceAll("=", ""), encodeToString(in_58, Base64.NO_PADDING));
227         assertEquals(out_59.replaceAll("=", ""), encodeToString(in_59, Base64.NO_PADDING));
228         assertEquals(out_60.replaceAll("=", ""), encodeToString(in_60, Base64.NO_PADDING));
229         assertEquals(out_61.replaceAll("=", ""), encodeToString(in_61, Base64.NO_PADDING));
230 
231         assertEquals(out_56.replaceAll("\n", ""), encodeToString(in_56, Base64.NO_WRAP));
232         assertEquals(out_57.replaceAll("\n", ""), encodeToString(in_57, Base64.NO_WRAP));
233         assertEquals(out_58.replaceAll("\n", ""), encodeToString(in_58, Base64.NO_WRAP));
234         assertEquals(out_59.replaceAll("\n", ""), encodeToString(in_59, Base64.NO_WRAP));
235         assertEquals(out_60.replaceAll("\n", ""), encodeToString(in_60, Base64.NO_WRAP));
236         assertEquals(out_61.replaceAll("\n", ""), encodeToString(in_61, Base64.NO_WRAP));
237     }
238 
239     /**
240      * Tests that Base64.Encoder.encode() does correct handling of the
241      * tail for each call.
242      *
243      * This test is disabled because while it passes if you can get it
244      * to run, android's test infrastructure currently doesn't allow
245      * us to get at package-private members (Base64.Encoder in
246      * this case).
247      */
XXXtestEncodeInternal()248     public void XXXtestEncodeInternal() throws Exception {
249         byte[] input = { (byte) 0x61, (byte) 0x62, (byte) 0x63 };
250         byte[] output = new byte[100];
251 
252         Base64.Encoder encoder = new Base64.Encoder(Base64.NO_PADDING | Base64.NO_WRAP,
253                                                     output);
254 
255         encoder.process(input, 0, 3, false);
256         assertEquals("YWJj".getBytes(), 4, encoder.output, encoder.op);
257         assertEquals(0, encoder.tailLen);
258 
259         encoder.process(input, 0, 3, false);
260         assertEquals("YWJj".getBytes(), 4, encoder.output, encoder.op);
261         assertEquals(0, encoder.tailLen);
262 
263         encoder.process(input, 0, 1, false);
264         assertEquals(0, encoder.op);
265         assertEquals(1, encoder.tailLen);
266 
267         encoder.process(input, 0, 1, false);
268         assertEquals(0, encoder.op);
269         assertEquals(2, encoder.tailLen);
270 
271         encoder.process(input, 0, 1, false);
272         assertEquals("YWFh".getBytes(), 4, encoder.output, encoder.op);
273         assertEquals(0, encoder.tailLen);
274 
275         encoder.process(input, 0, 2, false);
276         assertEquals(0, encoder.op);
277         assertEquals(2, encoder.tailLen);
278 
279         encoder.process(input, 0, 2, false);
280         assertEquals("YWJh".getBytes(), 4, encoder.output, encoder.op);
281         assertEquals(1, encoder.tailLen);
282 
283         encoder.process(input, 0, 2, false);
284         assertEquals("YmFi".getBytes(), 4, encoder.output, encoder.op);
285         assertEquals(0, encoder.tailLen);
286 
287         encoder.process(input, 0, 1, true);
288         assertEquals("YQ".getBytes(), 2, encoder.output, encoder.op);
289     }
290 
291     private static final String lipsum =
292             "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
293             "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
294             "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
295             "urna, pharetra vitae consequat eget, adipiscing eu ante. " +
296             "Aliquam venenatis arcu nec nibh imperdiet tempor. In id dui " +
297             "eget lorem aliquam rutrum vel vitae eros. In placerat ornare " +
298             "pretium. Curabitur non fringilla mi. Fusce ultricies, turpis " +
299             "eu ultrices suscipit, ligula nisi consectetur eros, dapibus " +
300             "aliquet dui sapien a turpis. Donec ultricies varius ligula, " +
301             "ut hendrerit arcu malesuada at. Praesent sed elit pretium " +
302             "eros luctus gravida. In ac dolor lorem. Cras condimentum " +
303             "convallis elementum. Phasellus vel felis in nulla ultrices " +
304             "venenatis. Nam non tortor non orci convallis convallis. " +
305             "Nam tristique lacinia hendrerit. Pellentesque habitant morbi " +
306             "tristique senectus et netus et malesuada fames ac turpis " +
307             "egestas. Vivamus cursus, nibh eu imperdiet porta, magna " +
308             "ipsum mollis mauris, sit amet fringilla mi nisl eu mi. " +
309             "Phasellus posuere, leo at ultricies vehicula, massa risus " +
310             "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
311             "molestie dapibus commodo. Ut vel tellus at massa gravida " +
312             "semper non sed orci.";
313 
testInputStream()314     public void testInputStream() throws Exception {
315         int[] flagses = { Base64.DEFAULT,
316                           Base64.NO_PADDING,
317                           Base64.NO_WRAP,
318                           Base64.NO_PADDING | Base64.NO_WRAP,
319                           Base64.CRLF,
320                           Base64.URL_SAFE };
321         int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
322         Random rng = new Random(32176L);
323 
324         // Test input needs to be at least 2048 bytes to fill up the
325         // read buffer of Base64InputStream.
326         byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();
327 
328         for (int flags: flagses) {
329             byte[] encoded = Base64.encode(plain, flags);
330 
331             ByteArrayInputStream bais;
332             Base64InputStream b64is;
333             byte[] actual = new byte[plain.length * 2];
334             int ap;
335             int b;
336 
337             // ----- test decoding ("encoded" -> "plain") -----
338 
339             // read as much as it will give us in one chunk
340             bais = new ByteArrayInputStream(encoded);
341             b64is = new Base64InputStream(bais, flags);
342             ap = 0;
343             while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
344                 ap += b;
345             }
346             assertEquals(actual, ap, plain);
347 
348             // read individual bytes
349             bais = new ByteArrayInputStream(encoded);
350             b64is = new Base64InputStream(bais, flags);
351             ap = 0;
352             while ((b = b64is.read()) != -1) {
353                 actual[ap++] = (byte) b;
354             }
355             assertEquals(actual, ap, plain);
356 
357             // mix reads of variously-sized arrays with one-byte reads
358             bais = new ByteArrayInputStream(encoded);
359             b64is = new Base64InputStream(bais, flags);
360             ap = 0;
361             readloop: while (true) {
362                 int l = writeLengths[rng.nextInt(writeLengths.length)];
363                 if (l >= 0) {
364                     b = b64is.read(actual, ap, l);
365                     if (b == -1) break readloop;
366                     ap += b;
367                 } else {
368                     for (int i = 0; i < -l; ++i) {
369                         if ((b = b64is.read()) == -1) break readloop;
370                         actual[ap++] = (byte) b;
371                     }
372                 }
373             }
374             assertEquals(actual, ap, plain);
375 
376             // ----- test encoding ("plain" -> "encoded") -----
377 
378             // read as much as it will give us in one chunk
379             bais = new ByteArrayInputStream(plain);
380             b64is = new Base64InputStream(bais, flags, true);
381             ap = 0;
382             while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
383                 ap += b;
384             }
385             assertEquals(actual, ap, encoded);
386 
387             // read individual bytes
388             bais = new ByteArrayInputStream(plain);
389             b64is = new Base64InputStream(bais, flags, true);
390             ap = 0;
391             while ((b = b64is.read()) != -1) {
392                 actual[ap++] = (byte) b;
393             }
394             assertEquals(actual, ap, encoded);
395 
396             // mix reads of variously-sized arrays with one-byte reads
397             bais = new ByteArrayInputStream(plain);
398             b64is = new Base64InputStream(bais, flags, true);
399             ap = 0;
400             readloop: while (true) {
401                 int l = writeLengths[rng.nextInt(writeLengths.length)];
402                 if (l >= 0) {
403                     b = b64is.read(actual, ap, l);
404                     if (b == -1) break readloop;
405                     ap += b;
406                 } else {
407                     for (int i = 0; i < -l; ++i) {
408                         if ((b = b64is.read()) == -1) break readloop;
409                         actual[ap++] = (byte) b;
410                     }
411                 }
412             }
413             assertEquals(actual, ap, encoded);
414         }
415     }
416 
417     /** http://b/3026478 */
testSingleByteReads()418     public void testSingleByteReads() throws IOException {
419         InputStream in = new Base64InputStream(
420                 new ByteArrayInputStream("/v8=".getBytes()), Base64.DEFAULT);
421         assertEquals(254, in.read());
422         assertEquals(255, in.read());
423     }
424 
425     /**
426      * Tests that Base64OutputStream produces exactly the same results
427      * as calling Base64.encode/.decode on an in-memory array.
428      */
testOutputStream()429     public void testOutputStream() throws Exception {
430         int[] flagses = { Base64.DEFAULT,
431                           Base64.NO_PADDING,
432                           Base64.NO_WRAP,
433                           Base64.NO_PADDING | Base64.NO_WRAP,
434                           Base64.CRLF,
435                           Base64.URL_SAFE };
436         int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
437         Random rng = new Random(32176L);
438 
439         // Test input needs to be at least 1024 bytes to test filling
440         // up the write(int) buffer of Base64OutputStream.
441         byte[] plain = (lipsum + lipsum).getBytes();
442 
443         for (int flags: flagses) {
444             byte[] encoded = Base64.encode(plain, flags);
445 
446             ByteArrayOutputStream baos;
447             Base64OutputStream b64os;
448             byte[] actual;
449             int p;
450 
451             // ----- test encoding ("plain" -> "encoded") -----
452 
453             // one large write(byte[]) of the whole input
454             baos = new ByteArrayOutputStream();
455             b64os = new Base64OutputStream(baos, flags);
456             b64os.write(plain);
457             b64os.close();
458             actual = baos.toByteArray();
459             assertEquals(encoded, actual);
460 
461             // many calls to write(int)
462             baos = new ByteArrayOutputStream();
463             b64os = new Base64OutputStream(baos, flags);
464             for (int i = 0; i < plain.length; ++i) {
465                 b64os.write(plain[i]);
466             }
467             b64os.close();
468             actual = baos.toByteArray();
469             assertEquals(encoded, actual);
470 
471             // intermixed sequences of write(int) with
472             // write(byte[],int,int) of various lengths.
473             baos = new ByteArrayOutputStream();
474             b64os = new Base64OutputStream(baos, flags);
475             p = 0;
476             while (p < plain.length) {
477                 int l = writeLengths[rng.nextInt(writeLengths.length)];
478                 l = Math.min(l, plain.length-p);
479                 if (l >= 0) {
480                     b64os.write(plain, p, l);
481                     p += l;
482                 } else {
483                     l = Math.min(-l, plain.length-p);
484                     for (int i = 0; i < l; ++i) {
485                         b64os.write(plain[p+i]);
486                     }
487                     p += l;
488                 }
489             }
490             b64os.close();
491             actual = baos.toByteArray();
492             assertEquals(encoded, actual);
493 
494             // ----- test decoding ("encoded" -> "plain") -----
495 
496             // one large write(byte[]) of the whole input
497             baos = new ByteArrayOutputStream();
498             b64os = new Base64OutputStream(baos, flags, false);
499             b64os.write(encoded);
500             b64os.close();
501             actual = baos.toByteArray();
502             assertEquals(plain, actual);
503 
504             // many calls to write(int)
505             baos = new ByteArrayOutputStream();
506             b64os = new Base64OutputStream(baos, flags, false);
507             for (int i = 0; i < encoded.length; ++i) {
508                 b64os.write(encoded[i]);
509             }
510             b64os.close();
511             actual = baos.toByteArray();
512             assertEquals(plain, actual);
513 
514             // intermixed sequences of write(int) with
515             // write(byte[],int,int) of various lengths.
516             baos = new ByteArrayOutputStream();
517             b64os = new Base64OutputStream(baos, flags, false);
518             p = 0;
519             while (p < encoded.length) {
520                 int l = writeLengths[rng.nextInt(writeLengths.length)];
521                 l = Math.min(l, encoded.length-p);
522                 if (l >= 0) {
523                     b64os.write(encoded, p, l);
524                     p += l;
525                 } else {
526                     l = Math.min(-l, encoded.length-p);
527                     for (int i = 0; i < l; ++i) {
528                         b64os.write(encoded[p+i]);
529                     }
530                     p += l;
531                 }
532             }
533             b64os.close();
534             actual = baos.toByteArray();
535             assertEquals(plain, actual);
536         }
537     }
538 
testOutputStream_ioExceptionDuringClose()539     public void testOutputStream_ioExceptionDuringClose() {
540         OutputStream out = new OutputStream() {
541             @Override public void write(int b) throws IOException { }
542             @Override public void close() throws IOException {
543                 throw new IOException("close()");
544             }
545         };
546         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
547         try {
548             out2.close();
549             fail();
550         } catch (IOException expected) {
551         }
552     }
553 
testOutputStream_ioExceptionDuringCloseAndWrite()554     public void testOutputStream_ioExceptionDuringCloseAndWrite() {
555         OutputStream out = new OutputStream() {
556             @Override public void write(int b) throws IOException {
557                 throw new IOException("write()");
558             }
559             @Override public void write(byte[] b) throws IOException {
560                 throw new IOException("write()");
561             }
562             @Override public void write(byte[] b, int off, int len) throws IOException {
563                 throw new IOException("write()");
564             }
565             @Override public void close() throws IOException {
566                 throw new IOException("close()");
567             }
568         };
569         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
570         try {
571             out2.close();
572             fail();
573         } catch (IOException expected) {
574             // Base64OutputStream write()s pending (possibly empty) data
575             // before close(), so the IOE from write() should be thrown and
576             // any later exception suppressed.
577             assertEquals("write()", expected.getMessage());
578             Throwable[] suppressed = expected.getSuppressed();
579             List<String> suppressedMessages = Arrays.asList(suppressed).stream()
580                     .map((e) -> e.getMessage())
581                     .collect(Collectors.toList());
582             assertEquals(Collections.singletonList("close()"), suppressedMessages);
583         }
584     }
585 
testOutputStream_ioExceptionDuringWrite()586     public void testOutputStream_ioExceptionDuringWrite() {
587         OutputStream out = new OutputStream() {
588             @Override public void write(int b) throws IOException {
589                 throw new IOException("write()");
590             }
591             @Override public void write(byte[] b) throws IOException {
592                 throw new IOException("write()");
593             }
594             @Override public void write(byte[] b, int off, int len) throws IOException {
595                 throw new IOException("write()");
596             }
597         };
598         // Base64OutputStream write()s pending (possibly empty) data
599         // before close(), so the IOE from write() should be thrown.
600         OutputStream out2 = new Base64OutputStream(out, Base64.DEFAULT);
601         try {
602             out2.close();
603             fail();
604         } catch (IOException expected) {
605         }
606     }
607 
608 }
609