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