Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 27a31e57 authored by Doug Zongker's avatar Doug Zongker
Browse files

add Base64InputStream

Change-Id: I777b54bd6d01c86105b473a6701a06d350cee8d1
parent 0b390031
Loading
Loading
Loading
Loading
+169 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.common;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * An OutputStream that does either Base64 encoding or decoding on the
 * data written to it, writing the resulting data to another
 * OutputStream.
 */
public class Base64InputStream extends FilterInputStream {
    private final boolean encode;
    private final Base64.EncoderState estate;
    private final Base64.DecoderState dstate;

    private static byte[] EMPTY = new byte[0];

    private static final int BUFFER_SIZE = 2048;
    private boolean eof;
    private byte[] inputBuffer;
    private byte[] outputBuffer;
    private int outputStart;
    private int outputEnd;

    /**
     * An InputStream that performs Base64 decoding on the data read
     * from the wrapped stream.
     *
     * @param in the InputStream to read the source data from
     * @param flags bit flags for controlling the decoder; see the
     *        constants in {@link Base64}
     */
    public Base64InputStream(InputStream out, int flags) {
        this(out, flags, false);
    }

    /**
     * Performs Base64 encoding or decoding on the data read from the
     * wrapped InputStream.
     *
     * @param in the InputStream to read the source data from
     * @param flags bit flags for controlling the decoder; see the
     *        constants in {@link Base64}
     * @param encode true to encode, false to decode
     */
    public Base64InputStream(InputStream out, int flags, boolean encode) {
        super(out);
        this.encode = encode;
        eof = false;
        inputBuffer = new byte[BUFFER_SIZE];
        if (encode) {
            // len*8/5+10 is an overestimate of the most bytes the
            // encoder can produce for len bytes of input.
            outputBuffer = new byte[BUFFER_SIZE * 8/5 + 10];
            estate = new Base64.EncoderState(flags, outputBuffer);
            dstate = null;
        } else {
            // len*3/4+10 is an overestimate of the most bytes the
            // decoder can produce for len bytes of input.
            outputBuffer = new byte[BUFFER_SIZE * 3/4 + 10];
            estate = null;
            dstate = new Base64.DecoderState(flags, outputBuffer);
        }
        outputStart = 0;
        outputEnd = 0;
    }

    public boolean markSupported() {
        return false;
    }

    public void mark(int readlimit) {
        throw new UnsupportedOperationException();
    }

    public void reset() {
        throw new UnsupportedOperationException();
    }

    public void close() throws IOException {
        in.close();
        inputBuffer = null;
    }

    public int available() {
        return outputEnd - outputStart;
    }

    public long skip(long n) throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return 0;
        }
        long bytes = Math.min(n, outputEnd-outputStart);
        outputStart += bytes;
        return bytes;
    }

    public int read() throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return -1;
        } else {
            return outputBuffer[outputStart++];
        }
    }

    public int read(byte[] b, int off, int len) throws IOException {
        if (outputStart >= outputEnd) {
            refill();
        }
        if (outputStart >= outputEnd) {
            return -1;
        }
        int bytes = Math.min(len, outputEnd-outputStart);
        System.arraycopy(outputBuffer, outputStart, b, off, bytes);
        outputStart += bytes;
        return bytes;
    }

    /**
     * Read data from the input stream into inputBuffer, then
     * decode/encode it into the empty outputBuffer, and reset the
     * outputStart and outputEnd pointers.
     */
    private void refill() throws IOException {
        if (eof) return;
        int bytesRead = in.read(inputBuffer);
        if (encode) {
            if (bytesRead == -1) {
                eof = true;
                Base64.encodeInternal(EMPTY, 0, 0, estate, true);
            } else {
                Base64.encodeInternal(inputBuffer, 0, bytesRead, estate, false);
            }
            outputEnd = estate.op;
        } else {
            if (bytesRead == -1) {
                eof = true;
                Base64.decodeInternal(EMPTY, 0, 0, dstate, true);
            } else {
                Base64.decodeInternal(inputBuffer, 0, bytesRead, dstate, false);
            }
            outputEnd = dstate.op;
        }
        outputStart = 0;
    }
}
+163 −53
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.common;

import junit.framework.TestCase;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Random;

@@ -228,8 +229,13 @@ public class Base64Test extends TestCase {
    /**
     * Tests that Base64.encodeInternal does correct handling of the
     * tail for each call.
     *
     * This test is disabled because while it passes if you can get it
     * to run, android's test infrastructure currently doesn't allow
     * us to get at package-private members (Base64.EncoderState in
     * this case).
     */
    public void testEncodeInternal() throws Exception {
    public void XXXtestEncodeInternal() throws Exception {
        byte[] input = { (byte) 0x61, (byte) 0x62, (byte) 0x63 };
        byte[] output = new byte[100];

@@ -272,23 +278,8 @@ public class Base64Test extends TestCase {
        assertEquals("YQ".getBytes(), 2, state.output, state.op);
    }

    /**
     * Tests that Base64OutputStream produces exactly the same results
     * as calling Base64.encode/.decode on an in-memory array.
     */
    public void testOutputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // input needs to be at least 1024 bytes to test filling up
        // the write(int) buffer.
        byte[] input = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
    private static final String lipsum =
            "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
            "Quisque congue eleifend odio, eu ornare nulla facilisis eget. " +
            "Integer eget elit diam, sit amet laoreet nibh. Quisque enim " +
            "urna, pharetra vitae consequat eget, adipiscing eu ante. " +
@@ -308,103 +299,222 @@ public class Base64Test extends TestCase {
            "Phasellus posuere, leo at ultricies vehicula, massa risus " +
            "volutpat sapien, eu tincidunt diam ipsum eget nulla. Cras " +
            "molestie dapibus commodo. Ut vel tellus at massa gravida " +
                        "semper non sed orci.").getBytes();
            "semper non sed orci.";

    public void testInputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // Test input needs to be at least 2048 bytes to fill up the
        // read buffer of Base64InputStream.
        byte[] plain = (lipsum + lipsum + lipsum + lipsum + lipsum).getBytes();

        for (int flags: flagses) {
            byte[] encoded = Base64.encode(plain, flags);

            ByteArrayInputStream bais;
            Base64InputStream b64is;
            byte[] actual = new byte[plain.length * 2];
            int ap;
            int b;

            // ----- test decoding ("encoded" -> "plain") -----

            // read as much as it will give us in one chunk
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
                ap += b;
            }
            assertEquals(actual, ap, plain);

            // read individual bytes
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            while ((b = b64is.read()) != -1) {
                actual[ap++] = (byte) b;
            }
            assertEquals(actual, ap, plain);

            // mix reads of variously-sized arrays with one-byte reads
            bais = new ByteArrayInputStream(encoded);
            b64is = new Base64InputStream(bais, flags);
            ap = 0;
            readloop: while (true) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                if (l >= 0) {
                    b = b64is.read(actual, ap, l);
                    if (b == -1) break readloop;
                    ap += b;
                } else {
                    for (int i = 0; i < -l; ++i) {
                        if ((b = b64is.read()) == -1) break readloop;
                        actual[ap++] = (byte) b;
                    }
                }
            }
            assertEquals(actual, ap, plain);

        for (int f = 0; f < flagses.length; ++f) {
            int flags = flagses[f];
            // ----- test encoding ("plain" -> "encoded") -----

            byte[] expected = Base64.encode(input, flags);
            // read as much as it will give us in one chunk
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            while ((b = b64is.read(actual, ap, actual.length-ap)) != -1) {
                ap += b;
            }
            assertEquals(actual, ap, encoded);

            // read individual bytes
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            while ((b = b64is.read()) != -1) {
                actual[ap++] = (byte) b;
            }
            assertEquals(actual, ap, encoded);

            // mix reads of variously-sized arrays with one-byte reads
            bais = new ByteArrayInputStream(plain);
            b64is = new Base64InputStream(bais, flags, true);
            ap = 0;
            readloop: while (true) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                if (l >= 0) {
                    b = b64is.read(actual, ap, l);
                    if (b == -1) break readloop;
                    ap += b;
                } else {
                    for (int i = 0; i < -l; ++i) {
                        if ((b = b64is.read()) == -1) break readloop;
                        actual[ap++] = (byte) b;
                    }
                }
            }
            assertEquals(actual, ap, encoded);
        }
    }

    /**
     * Tests that Base64OutputStream produces exactly the same results
     * as calling Base64.encode/.decode on an in-memory array.
     */
    public void testOutputStream() throws Exception {
        int[] flagses = { Base64.DEFAULT,
                          Base64.NO_PADDING,
                          Base64.NO_WRAP,
                          Base64.NO_PADDING | Base64.NO_WRAP,
                          Base64.CRLF,
                          Base64.WEB_SAFE };
        int[] writeLengths = { -10, -5, -1, 0, 1, 1, 2, 2, 3, 10, 100 };
        Random rng = new Random(32176L);

        // Test input needs to be at least 1024 bytes to test filling
        // up the write(int) buffer of Base64OutputStream.
        byte[] plain = (lipsum + lipsum).getBytes();

        for (int flags: flagses) {
            byte[] encoded = Base64.encode(plain, flags);

            ByteArrayOutputStream baos;
            Base64OutputStream b64os;
            byte[] actual;
            int p;

            // ----- test encoding ("input" -> "expected") -----
            // ----- test encoding ("plain" -> "encoded") -----

            // one large write(byte[]) of the whole input
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            b64os.write(input);
            b64os.write(plain);
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // many calls to write(int)
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            for (int i = 0; i < input.length; ++i) {
                b64os.write(input[i]);
            for (int i = 0; i < plain.length; ++i) {
                b64os.write(plain[i]);
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // intermixed sequences of write(int) with
            // write(byte[],int,int) of various lengths.
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags);
            p = 0;
            while (p < input.length) {
            while (p < plain.length) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                l = Math.min(l, input.length-p);
                l = Math.min(l, plain.length-p);
                if (l >= 0) {
                    b64os.write(input, p, l);
                    b64os.write(plain, p, l);
                    p += l;
                } else {
                    l = Math.min(-l, input.length-p);
                    l = Math.min(-l, plain.length-p);
                    for (int i = 0; i < l; ++i) {
                        b64os.write(input[p+i]);
                        b64os.write(plain[p+i]);
                    }
                    p += l;
                }
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(expected, actual);
            assertEquals(encoded, actual);

            // ----- test decoding ("expected" -> "input") -----
            // ----- test decoding ("encoded" -> "plain") -----

            // one large write(byte[]) of the whole input
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            b64os.write(expected);
            b64os.write(encoded);
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);

            // many calls to write(int)
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            for (int i = 0; i < expected.length; ++i) {
                b64os.write(expected[i]);
            for (int i = 0; i < encoded.length; ++i) {
                b64os.write(encoded[i]);
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);

            // intermixed sequences of write(int) with
            // write(byte[],int,int) of various lengths.
            baos = new ByteArrayOutputStream();
            b64os = new Base64OutputStream(baos, flags, false);
            p = 0;
            while (p < expected.length) {
            while (p < encoded.length) {
                int l = writeLengths[rng.nextInt(writeLengths.length)];
                l = Math.min(l, expected.length-p);
                l = Math.min(l, encoded.length-p);
                if (l >= 0) {
                    b64os.write(expected, p, l);
                    b64os.write(encoded, p, l);
                    p += l;
                } else {
                    l = Math.min(-l, expected.length-p);
                    l = Math.min(-l, encoded.length-p);
                    for (int i = 0; i < l; ++i) {
                        b64os.write(expected[p+i]);
                        b64os.write(encoded[p+i]);
                    }
                    p += l;
                }
            }
            b64os.close();
            actual = baos.toByteArray();
            assertEquals(input, actual);
            assertEquals(plain, actual);
        }
    }
}