/*
 *  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.
 */


package org.apache.harmony.security.provider.crypto;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessController;
import java.security.ProviderException;


/**
 *  The static class providing access on Linux platform
 *  to system means for generating true random bits. <BR>
 *
 *  The source for true random bits is one of Linux's devices "/dev/urandom" or
 *  "/dev/random" depends on which one is available; if both the first is used. <BR>
 *
 *  If no device available the service is not available,
 *  that is, provider shouldn't register the algorithm. <BR>
 */


public class RandomBitsSupplier implements SHA1_Data {


    /**
     *  BufferedInputStream to read from device
     */
    // BEGIN android-changed
    // Using a BufferedInputStream leads to problems
    // on Android in rare cases, since the
    // BufferedInputStream's available() issues an
    // iotcl(), and the pseudo device doesn't seem
    // to like that. Since we're reading bigger
    // chunks and not single bytes, the FileInputStream
    // shouldn't be slower, so we use that. Same might
    // apply to other Linux platforms.
    private static FileInputStream bis = null;
    // END android-changed

    /**
     * File to connect to device
     */
    private static File randomFile = null;

    /**
     * value of field is "true" only if a device is available
     */
    private static boolean serviceAvailable = false;


    static {
        AccessController.doPrivileged(
            new java.security.PrivilegedAction() {
                public Object run() {

                    for ( int i = 0 ; i < DEVICE_NAMES.length ; i++ ) {
                        File file = new File(DEVICE_NAMES[i]);

                        try {
                            if ( file.canRead() ) {
                                // BEGIN android-modified
                                bis = new FileInputStream(file);
                                // END android-modified
                                randomFile = file;
                                serviceAvailable = true;
                                return null;
                            }
                        } catch (FileNotFoundException e) {
                        }
                    }

                    // BEGIN android-removed
//                    // If we have come out of the above loop, then we have been unable to
//                    // access /dev/*random, so try to fall back to using the system random() API
//                    try {
//                        System.loadLibrary(LIBRARY_NAME);
//                        serviceAvailable = true;
//                    } catch (UnsatisfiedLinkError e) {
//                        serviceAvailable = false;
//                    }
                    return null;
                }
            }
        );
    }


    /**
     * The method is called by provider to determine if a device is available.
     */
    static boolean isServiceAvailable() {
        return serviceAvailable;
    }


    /**
     * On platforms with "random" devices available,
     * the method reads random bytes from the device.  <BR>
     *
     * In case of any runtime failure ProviderException gets thrown.
     */
    private static synchronized byte[] getUnixDeviceRandom(int numBytes) {

        byte[] bytes = new byte[numBytes];

        int total = 0;
        int bytesRead;
        int offset = 0;
        try {
            for ( ; ; ) {

                bytesRead = bis.read(bytes, offset, numBytes-total);


                // the below case should not occur because /dev/random or /dev/urandom is a special file
                // hence, if it is happened there is some internal problem
                if ( bytesRead == -1 ) {
                    throw new ProviderException("bytesRead == -1");
                }

                total  += bytesRead;
                offset += bytesRead;

                if ( total >= numBytes ) {
                    break;
                }
            }
        } catch (IOException e) {

            // actually there should be no IOException because device is a special file;
            // hence, there is either some internal problem or, for instance,
            // device was removed in runtime, or something else
            throw new ProviderException("ATTENTION: IOException in RandomBitsSupplier.getLinuxRandomBits(): " + e);
        }
        return bytes;
    }


    // BEGIN android-removed
//    /**
//     * On platforms with no "random" devices available, this native
//     * method uses system API calls to generate random numbers<BR>
//     *
//     * In case of any runtime failure ProviderException gets thrown.
//     */
//    private static native synchronized boolean getUnixSystemRandom(byte[] randomBits, int numBytes);
    // END android-removed


    /**
     * The method returns byte array of requested length provided service is available.
     * ProviderException gets thrown otherwise.
     *
     * @param
     *       numBytes - length of bytes requested
     * @return
     *       byte array
     * @throws
     *       InvalidArgumentException - if numBytes <= 0
     */
    public static byte[] getRandomBits(int numBytes) {

        if (numBytes <= 0) {
            throw new IllegalArgumentException(Integer.toString(numBytes));
        }

        // We have been unable to get a random device or fall back to the
        // native security module code - throw an exception.
        if ( !serviceAvailable ) {
            throw new ProviderException("ATTENTION: service is not available : no random devices");
        }

        // BEGIN android-changed
        return getUnixDeviceRandom(numBytes);
        // END android-changed
    }
}
