/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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.
 */

/**
* @author Vladimir N. Molotkov
* @version $Revision$
*/

package org.apache.harmony.security.tests.java.security;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

import junit.framework.TestCase;

import org.apache.harmony.security.tests.support.MDGoldenData;

import dalvik.annotation.TestLevel;
import dalvik.annotation.TestTargetClass;
import dalvik.annotation.TestTargetNew;

@TestTargetClass(DigestInputStream.class)
/**
 * Tests for fields and methods of class <code>DigestInputStream</code>
 *
 */
public class DigestInputStreamTest extends TestCase {

    /**
     * Message digest algorithm name used during testing
     */
    private static final String algorithmName[] = {
            "SHA-1",
            "SHA",
            "SHA1",
            "SHA-256",
            "SHA-384",
            "SHA-512",
            "MD5",
    };
    /**
     * Chunk size for read(byte, off, len) tests
     */
    private static final int CHUNK_SIZE = 32;
    /**
     * Test message for digest computations
     */
    private static final byte[] myMessage = MDGoldenData.getMessage();
    /**
     * The length of test message
     */
    private static final int MY_MESSAGE_LEN = myMessage.length;

    //
    // Tests
    //

    /**
     * Test #1 for <code>DigestInputStream</code> constructor<br>
     *
     * Assertion: creates new <code>DigestInputStream</code> instance
     * using valid parameters (both non <code>null</code>)
     *
     * @throws NoSuchAlgorithmException
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "Verifies both non null parameters",
        method = "DigestInputStream",
        args = {java.io.InputStream.class, java.security.MessageDigest.class}
    )
    public final void testDigestInputStream01()  {
        for (int i=0; i<algorithmName.length; i++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[i]);
                InputStream is = new ByteArrayInputStream(myMessage);
                InputStream dis = new DigestInputStream(is, md);
                assertTrue(dis instanceof DigestInputStream);
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #2 for <code>DigestInputStream</code> constructor<br>
     *
     * Assertion: creates new <code>DigestInputStream</code> instance
     * using valid parameters (both <code>null</code>)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Verifies both null parameters. Need cases where just one parameter null",
        method = "DigestInputStream",
        args = {java.io.InputStream.class, java.security.MessageDigest.class}
    )
    public final void testDigestInputStream02() {
        InputStream dis = new DigestInputStream(null, null);
        assertTrue(dis instanceof DigestInputStream);
    }

    /**
     * Test #1 for <code>read()</code> method<br>
     *
     * Assertion: returns the byte read<br>
     * Assertion: updates associated digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "read",
        args = {}
    )
    public final void testRead01()
        throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                for (int i=0; i<MY_MESSAGE_LEN; i++) {
                    // check that read() returns valid values
                    assertTrue("retval", ((byte)dis.read() == myMessage[i]));
                }
                // check that associated digest has been updated properly
                assertTrue("update",
                        Arrays.equals(
                                dis.getMessageDigest().digest(),
                                MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #2 for <code>read()</code> method<br>
     *
     * Assertion: returns -1 if EOS had been
     * reached but not read before method call<br>
     *
     * Assertion: must not update digest if EOS had been
     * reached but not read before method call<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "read",
        args = {}
    )
    public final void testRead02()
        throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                for (int i=0; i<MY_MESSAGE_LEN; i++) {
                    dis.read();
                }
                // check that subsequent read() calls return -1 (eos)
                assertEquals("retval1", -1, dis.read());
                assertEquals("retval2", -1, dis.read());
                assertEquals("retval3", -1, dis.read());
                // check that 3 previous read() calls did not update digest
                assertTrue("update",
                        Arrays.equals(dis.getMessageDigest().digest(),
                                MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #3 for <code>read()</code> method<br>
     * Test #1 for <code>on(boolean)</code> method<br>
     *
     * Assertion: <code>read()</code> must not update digest if it is off<br>
     * Assertion: <code>on(boolean)</code> turns digest functionality on
     * (if <code>true</code> passed as a parameter) or off (if <code>false</code>
     *  passed)
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "read",
        args = {}
    )
    public final void testRead03()
        throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);

                // turn digest off
                dis.on(false);

                for (int i=0; i<MY_MESSAGE_LEN; i++) {
                    dis.read();
                }

                // check that digest value has not been updated by read()
                assertTrue(Arrays.equals(dis.getMessageDigest().digest(),
                        MDGoldenData.getDigest(algorithmName[ii]+"_NU")));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #4 for <code>read()</code> method<br>
     *
     * Assertion: broken <code>DigestInputStream</code>instance:
     * <code>InputStream</code> not set. <code>read()</code> must
     * not work
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "IOException is not tested",
        method = "read",
        args = {}
    )
    public final void testRead04() throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                DigestInputStream dis = new DigestInputStream(null, md);
                // must result in an exception
                try {
                    for (int i=0; i<MY_MESSAGE_LEN; i++) {
                        dis.read();
                    }
                } catch (Exception e) {
                    // Expected.
                    return;
                }

                fail("InputStream not set. read() must not work");

            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #5 for <code>read()</code> method<br>
     *
     * Assertion: broken <code>DigestInputStream</code>instance:
     * associated <code>MessageDigest</code> not set.
     * <code>read()</code> must not work when digest
     * functionality is on
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "IOException is not tested",
        method = "read",
        args = {}
    )
    public final void testRead05() {
        InputStream is = new ByteArrayInputStream(myMessage);
        DigestInputStream dis = new DigestInputStream(is, null);

        // must result in an exception
        try {
            for (int i=0; i<MY_MESSAGE_LEN; i++) {
                dis.read();
            }
            fail("read() must not work when digest functionality is on");
        } catch (Exception e) {
            // Expected.
        }
    }

    /**
     * Test #6 for <code>read()</code> method<br>
     * Test #2 for <code>on(boolean)</code> method<br>
     *
     * Assertion: broken <code>DigestInputStream</code>instance:
     * associated <code>MessageDigest</code> not set.
     * <code>read()</code> must work when digest
     * functionality is off
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        notes = "",
        method = "read",
        args = {}
    )
    public final void testRead06()
        throws IOException {
        InputStream is = new ByteArrayInputStream(myMessage);
        // construct object without digest
        DigestInputStream dis = new DigestInputStream(is, null);
        // set digest functionality to off
        dis.on(false);
        // the following must pass without any exception
        for (int i=0; i<MY_MESSAGE_LEN; i++) {
            assertTrue((byte)dis.read() == myMessage[i]);
        }
    }

    /**
     * Test #1 for <code>read(byte[],int,int)</code> method<br>
     *
     * Assertion: returns the number of bytes read<br>
     *
     * Assertion: put bytes read into specified array at specified offset<br>
     *
     * Assertion: updates associated digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        method = "read",
        args = {byte[].class, int.class, int.class}
    )
    public final void testReadbyteArrayintint01()
        throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                byte[] bArray = new byte[MY_MESSAGE_LEN];

                // check that read(byte[],int,int) returns valid value
                assertTrue("retval",
                        dis.read(bArray, 0, bArray.length) == MY_MESSAGE_LEN);
                // check that bArray has been filled properly
                assertTrue("bArray", Arrays.equals(myMessage, bArray));
                // check that associated digest has been updated properly
                assertTrue("update", Arrays.equals(dis.getMessageDigest().digest(),
                        MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #2 for <code>read(byte[],int,int)</code> method<br>
     *
     * Assertion: returns the number of bytes read<br>
     *
     * Assertion: put bytes read into specified array at specified offset<br>
     *
     * Assertion: updates associated digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        method = "read",
        args = {byte[].class, int.class, int.class}
    )
    public final void testReadbyteArrayintint02()
        throws IOException {
        // check precondition
        assertEquals(0, MY_MESSAGE_LEN % CHUNK_SIZE);

        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                byte[] bArray = new byte[MY_MESSAGE_LEN];

                for (int i=0; i<MY_MESSAGE_LEN/CHUNK_SIZE; i++) {
                    // check that read(byte[],int,int) returns valid value
                    assertTrue("retval",
                            dis.read(bArray, i*CHUNK_SIZE, CHUNK_SIZE) == CHUNK_SIZE);
                }
                // check that bArray has been filled properly
                assertTrue("bArray", Arrays.equals(myMessage, bArray));
                // check that associated digest has been updated properly
                assertTrue("update", Arrays.equals(dis.getMessageDigest().digest(),
                        MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }


    /**
     * Test #3 for <code>read(byte[],int,int)</code> method<br>
     *
     * Assertion: returns the number of bytes read<br>
     *
     * Assertion: put bytes read into specified array at specified offset<br>
     *
     * Assertion: updates associated digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        method = "read",
        args = {byte[].class, int.class, int.class}
    )
    public final void testReadbyteArrayintint03()
        throws IOException {
        // check precondition
        assertTrue(MY_MESSAGE_LEN % (CHUNK_SIZE+1) != 0);

        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                byte[] bArray = new byte[MY_MESSAGE_LEN];

                for (int i=0; i<MY_MESSAGE_LEN/(CHUNK_SIZE+1); i++) {
                    // check that read(byte[],int,int) returns valid value
                    assertTrue("retval1",
                            dis.read(bArray, i*(CHUNK_SIZE+1), CHUNK_SIZE+1) ==
                                CHUNK_SIZE + 1);
                }

                // check that last call returns right
                // number of remaining bytes
                assertTrue("retval2",
                        dis.read(bArray,
                                MY_MESSAGE_LEN/(CHUNK_SIZE+1)*(CHUNK_SIZE+1),
                                MY_MESSAGE_LEN % (CHUNK_SIZE+1)) ==
                                    (MY_MESSAGE_LEN % (CHUNK_SIZE+1)));

                // check that bArray has been filled properly
                assertTrue("bArray", Arrays.equals(myMessage, bArray));
                // check that associated digest has been updated properly
                assertTrue("update", Arrays.equals(dis.getMessageDigest().digest(),
                        MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #4 for <code>read(byte[],int,int)</code> method<br>
     *
     * Assertion: returns the number of bytes read<br>
     *
     * Assertion: updates associated digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        method = "read",
        args = {byte[].class, int.class, int.class}
    )
    public final void testReadbyteArrayintint04()
        throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                byte[] bArray = new byte[MY_MESSAGE_LEN];
                // read all but EOS
                dis.read(bArray, 0, bArray.length);
                // check that subsequent read(byte[],int,int) calls return -1 (EOS)
                assertEquals("retval1", -1, dis.read(bArray, 0, 1));
                assertEquals("retval2", -1, dis.read(bArray, 0, bArray.length));
                assertEquals("retval3", -1, dis.read(bArray, 0, 1));
                // check that 3 previous read() calls did not update digest
                assertTrue("update",
                        Arrays.equals(dis.getMessageDigest().digest(),
                                MDGoldenData.getDigest(algorithmName[ii])));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test #5 for <code>read(byte[],int,int)</code> method<br>
     *
     * Assertion: returns the number of bytes read<br>
     *
     * Assertion: put bytes read into specified array at specified offset<br>
     *
     * Assertion: does not update associated digest if
     * digest functionality is off<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL_COMPLETE,
        method = "read",
        args = {byte[].class, int.class, int.class}
    )
    public final void testReadbyteArrayintint05()
        throws IOException {
        // check precondition
        assertEquals(0, MY_MESSAGE_LEN % CHUNK_SIZE);

        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);
                byte[] bArray = new byte[MY_MESSAGE_LEN];

                // turn digest off
                dis.on(false);

                for (int i=0; i<MY_MESSAGE_LEN/CHUNK_SIZE; i++) {
                    dis.read(bArray, i*CHUNK_SIZE, CHUNK_SIZE);
                }
                // check that digest has not been updated
                assertTrue(Arrays.equals(dis.getMessageDigest().digest(),
                        MDGoldenData.getDigest(algorithmName[ii]+"_NU")));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test for <code>getMessageDigest()</code> method<br>
     *
     * Assertion: returns associated message digest<br>
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "getMessageDigest",
        args = {}
    )
    public final void testGetMessageDigest() {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                DigestInputStream dis = new DigestInputStream(null, md);

                assertTrue(dis.getMessageDigest() == md);
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }


    /**
     * Test for <code>setMessageDigest()</code> method<br>
     *
     * Assertion: set associated message digest<br>
     */
    @TestTargetNew(
        level = TestLevel.PARTIAL,
        notes = "Method setMessageDigest is not tested with null parameter",
        method = "setMessageDigest",
        args = {java.security.MessageDigest.class}
    )
    public final void testSetMessageDigest() {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                DigestInputStream dis = new DigestInputStream(null, null);
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                dis.setMessageDigest(md);

                assertTrue(dis.getMessageDigest() == md);
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test for <code>on()</code> method<br>
     * Assertion: turns digest functionality on or off
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "on",
        args = {boolean.class}
    )
    public final void testOn() throws IOException {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);

                // turn digest off
                dis.on(false);

                for (int i=0; i<MY_MESSAGE_LEN-1; i++) {
                    dis.read();
                }

                // turn digest on
                dis.on(true);

                // read remaining byte
                dis.read();

                byte[] digest = dis.getMessageDigest().digest();

                // check that digest value has been
                // updated by the last read() call
                assertFalse(
                        Arrays.equals(digest,MDGoldenData.getDigest(algorithmName[ii])) ||
                        Arrays.equals(digest,MDGoldenData.getDigest(algorithmName[ii]+"_NU")));
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

    /**
     * Test for <code>toString()</code> method<br>
     * Assertion: returns <code>String</code> representation of this object
     */
    @TestTargetNew(
        level = TestLevel.COMPLETE,
        notes = "",
        method = "toString",
        args = {}
    )
    public final void testToString() {
        for (int ii=0; ii<algorithmName.length; ii++) {
            try {
                MessageDigest md = MessageDigest.getInstance(algorithmName[ii]);
                InputStream is = new ByteArrayInputStream(myMessage);
                DigestInputStream dis = new DigestInputStream(is, md);

                assertNotNull(dis.toString());
                return;
            } catch (NoSuchAlgorithmException e) {
                // allowed failure
            }
        }
        fail(getName() + ": no MessageDigest algorithms available - test not performed");
    }

}
