All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.named_data.jndn.util.Common Maven / Gradle / Ivy

/**
 * Copyright (C) 2014-2019 Regents of the University of California.
 * @author: Jeff Thompson 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * A copy of the GNU Lesser General Public License is in the file COPYING.
 */

package net.named_data.jndn.util;

import java.io.File;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Date;
import java.util.Random;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import net.named_data.jndn.contrib.Murmur3;

/**
 * The Common class has static utility functions.
 */
public class Common {
  /**
   * Get the current time in milliseconds.
   * @return  The current time in milliseconds since 1/1/1970 UTC.
   */
  public static double
  getNowMilliseconds() { return (double)System.currentTimeMillis(); }

  /**
   * Get the library-wide random number generator. This method is synchronized so that multiple accesses to the
   * generator when a generator is not yet set will not throw an UnsupportedOperationException
   * @return the random number generator set in {@link #setRandom(Random)} or (by default) a SecureRandom
   */
  public static synchronized Random
  getRandom() {
    if(randomNumberGenerator_ == null){
      setRandom(new SecureRandom());
    }
    return randomNumberGenerator_;
  }

  /**
   * Set the library-wide random number generator; this method will only allow the generator to be set once.
   * Additionally, this method is thread-safe in that it guarantees that only the first caller will be able to set
   * the generator.
   * @param randomNumberGenerator the random number generator
   * @throws UnsupportedOperationException if a user attempts to set the generator a second time
   */
  public static synchronized void
  setRandom(Random randomNumberGenerator) {
    if(randomNumberGenerator_ == null) {
      randomNumberGenerator_ = randomNumberGenerator;
    }
    else{
      throw new UnsupportedOperationException("The random number generator may only be set once");
    }
  }

  /**
   * Compute the sha-256 digest of data.
   * @param data The input byte buffer. This does not change the position.
   * @return The digest.
   */
  public static byte[]
  digestSha256(ByteBuffer data)
  {
    MessageDigest sha256;
    try {
      sha256 = MessageDigest.getInstance("SHA-256");
    }
    catch (NoSuchAlgorithmException exception) {
      // Don't expect this to happen.
      throw new Error
        ("MessageDigest: SHA-256 is not supported: " + exception.getMessage());
    }
    int savePosition = data.position();
    sha256.update(data);
    data.position(savePosition);
    return sha256.digest();
  }

  /**
   * Compute the sha-256 digest of data.
   * @param data The input byte buffer.
   * @return The digest.
   */
  public static byte[]
  digestSha256(byte[] data)
  {
    MessageDigest sha256;
    try {
      sha256 = MessageDigest.getInstance("SHA-256");
    }
    catch (NoSuchAlgorithmException exception) {
      // Don't expect this to happen.
      throw new Error
        ("MessageDigest: SHA-256 is not supported: " + exception.getMessage());
    }
    sha256.update(data);
    return sha256.digest();
  }

  /**
   * Compute the HMAC with SHA-256 of data, as defined in
   * http://tools.ietf.org/html/rfc2104#section-2 .
   * @param key The key byte array.
   * @param data The input byte buffer.
   * @return The HMAC result.
   */
  public static byte[]
  computeHmacWithSha256(byte[] key, ByteBuffer data)
  {
    final String algorithm = "HmacSHA256";
    Mac mac;
    try {
      mac = Mac.getInstance(algorithm);
    }
    catch (NoSuchAlgorithmException ex) {
      // Don't expect this to happen.
      throw new Error
        ("computeHmac: " + algorithm + " is not supported: " + ex.getMessage());
    }

    try {
      mac.init(new SecretKeySpec(key, algorithm));
    } catch (InvalidKeyException ex) {
      // Don't expect this to happen.
      throw new Error
        ("computeHmac: Can't init " + algorithm + " with key: " + ex.getMessage());
    }
    int savePosition = data.position();
    mac.update(data);
    data.position(savePosition);
    return mac.doFinal();
  }

  /**
   * Compute the PBKDF2 with HMAC SHA1 of the password.
   * @param password The input password, which should have characters in the
   * range of 1 to 127.
   * @param salt The 8-byte salt.
   * @param nIterations The number of iterations of the hashing algorithm.
   * @param resultLength The number of bytes of the result array.
   * @return The result byte array.
   */
  public static byte[]
  computePbkdf2WithHmacSha1
    (char[] password, byte[] salt, int nIterations, int resultLength)
    throws NoSuchAlgorithmException, InvalidKeySpecException
  {
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance
      ("PBKDF2WithHmacSHA1");
    PBEKeySpec keySpec = new PBEKeySpec
      (password, salt, nIterations, resultLength * 8);
    return keyFactory.generateSecret(keySpec).getEncoded();
  }

  /**
   * Compute the PBKDF2 with HMAC SHA1 of the password.
   * @param password The input password, which should have values in the
   * range of 1 to 127.
   * @param salt The 8-byte salt.
   * @param nIterations The number of iterations of the hashing algorithm.
   * @param resultLength The number of bytes of the result array.
   * @return The result byte array.
   */
  public static byte[]
  computePbkdf2WithHmacSha1
    (byte[] password, byte[] salt, int nIterations, int resultLength)
    throws NoSuchAlgorithmException, InvalidKeySpecException
  {
    char[] charPassword = new char[password.length];
    for (int i = 0; i < password.length; ++i)
      charPassword[i] = (char)password[i];

    return computePbkdf2WithHmacSha1
      (charPassword, salt, nIterations, resultLength);
  }

  /**
   * Return a hex string of the contents of buffer.
   * @param buffer The buffer.
   * @return A string of hex bytes.
   */
  public static String
  toHex(byte[] buffer)
  {
    StringBuffer output = new StringBuffer(buffer.length * 2);
    for (int i = 0; i < buffer.length; ++i) {
      String hex = Integer.toHexString((int)buffer[i] & 0xff);
      if (hex.length() <= 1)
        // Append the leading zero.
        output.append("0");
      output.append(hex);
    }

    return output.toString();
  }

  /**
   * Encode the input as base64 using the appropriate base64Converter_ from
   * establishBase64Converter(), for ANDROID or Java 7+.
   * @param input The bytes to encode.
   * @return The base64 string.
   * @throws UnsupportedOperationException If can't establish a base64 converter for
   * this platform.
   */
  public static String
  base64Encode(byte[] input)
  {
    establishBase64Converter();

    try {
      if (base64ConverterType_ == Base64ConverterType.ANDROID)
        // Base64.NO_WRAP  is 2.
        return (String)base64Converter_.getDeclaredMethod
          ("encodeToString", byte[].class, int.class).invoke(null, input, 2);
      else if (base64ConverterType_ == Base64ConverterType.JAVA_UTIL) {
        Object encoder = base64Converter_.getDeclaredMethod
          ("getEncoder").invoke(null);
        return (String)encoder.getClass().getDeclaredMethod
          ("encodeToString", byte[].class).invoke(encoder, input);
      }
      else
        // Default to Base64ConverterType.JAVAX.
        return (String)base64Converter_.getDeclaredMethod
          ("printBase64Binary", byte[].class).invoke(null, input);
    } catch (Exception ex) {
      throw new UnsupportedOperationException("base64Encode: Error invoking method: " + ex);
    }
  }

  /**
   * Encode the input as base64 using the appropriate base64Converter_ from
   * establishBase64Converter(), for ANDROID or Java 7+.
   * @param input The bytes to encode.
   * @param addNewlines If true, add newlines to the output (good for writing to
   * a file).  If false, do not add newlines.
   * @return The base64 string.
   * @throws UnsupportedOperationException If can't establish a base64 converter for
   * this platform.
   */
  public static String
  base64Encode(byte[] input, boolean addNewlines)
  {
    String base64 = base64Encode(input);
    if (!addNewlines)
      return base64;

    StringBuffer result = new StringBuffer();
    for (int i = 0; i < base64.length(); i += 64) {
      int lineLength = 64;
      if (i + lineLength > base64.length())
        lineLength = base64.length() - i;

      result.append(base64, i, i + lineLength);
      result.append('\n');
    }

    return result.toString();
  }

  /**
   * Decode the input as base64 using the appropriate base64Converter_ from
   * establishBase64Converter(), for ANDROID or Java 7+.
   * @param encoding The base64 string.
   * @return The decoded bytes.
   * @throws UnsupportedOperationException If can't establish a base64 converter for
   * this platform.
   */
  public static byte[]
  base64Decode(String encoding) throws SecurityException
  {
    establishBase64Converter();

    try {
      if (base64ConverterType_ == Base64ConverterType.ANDROID)
        // Base64.DEFAULT is 0.
        return (byte[])base64Converter_.getDeclaredMethod
          ("decode", String.class, int.class).invoke(null, encoding, 0);
      else if (base64ConverterType_ == Base64ConverterType.JAVA_UTIL) {
        // java.util.Base64 doesn't like whitespace, so remove it.
        encoding = encoding.replaceAll("\\s", "");

        Object decoder = base64Converter_.getDeclaredMethod
          ("getDecoder").invoke(null);
        return (byte[])decoder.getClass().getDeclaredMethod
          ("decode", String.class).invoke(decoder, encoding);
      }
      else
        // Default to Base64ConverterType.JAVAX.
        return (byte[])base64Converter_.getDeclaredMethod
          ("parseBase64Binary", String.class).invoke(null, encoding);
    } catch (Exception ex) {
      throw new UnsupportedOperationException("base64Decode: Error invoking method: " + ex);
    }
  }

  /**
   * Convert the milliseconds to a Date object. This is a centralized utility
   * method to support portability.
   * @param millisecondsSince1970 The milliseconds since 1970.
   * @return A new Date object.
   */
  public static Date
  millisecondsSince1970ToDate(long millisecondsSince1970)
  {
    return new Date(millisecondsSince1970);
  }

  /**
   * Convert a Date object milliseconds. This is a centralized utility
   * method to support portability.
   * @param date the Date object.
   * @return The milliseconds since 1970.
   */
  public static long
  dateToMillisecondsSince1970(Date date)
  {
    return date.getTime();
  }

  /**
   * Return true if the platform is OS X.
   * @return True if OS X, false if not.
   */
  public static boolean
  platformIsOSX()
  {
    return System.getProperty("os.name").equals("Mac OS X");
  }

  /**
   * Get the user's home directory.
   * @return The home directory as a File object, or new File(".") if unknown.
   */
  public static File
  getHomeDirectory()
  {
    return new File(System.getProperty("user.home", "."));
  }

  /**
   * Get the current directory.
   * @return The current directory as a File object.
   */
  public static File
  getCurrentDirectory()
  {
    return new File(System.getProperty("user.dir", "."));
  }

  /**
   * Compute the MurmurHash3 of the data.
   * @param nHashSeed The hash seed.
   * @param dataToHash The input byte array to hash.
   * @return The hash value. This returns a long to make it easier to interpret
   * it as an unsigned 32-bit integer (instead of the Java int which is signed).
   */
  public static long
  murmurHash3(int nHashSeed, byte[] dataToHash)
  {
    int hash = Murmur3.hash32(dataToHash, dataToHash.length, nHashSeed);
    return hash >= 0 ? hash : 0x100000000L + hash;
  }

  /**
   * Compute the MurmurHash3 of the integer value.
   * @param nHashSeed The hash seed.
   * @param value The integer value, interpreted as a 4-byte little-endian array.
   * This ignores the upper 4 bytes of the long integer.
   * @return The hash value. This returns a Java int which is a 32-bit signed
   * integer, but it should be interpreted as a 32-bit unsigned integer.
   */
  public static long
  murmurHash3(int nHashSeed, long value)
  {
    byte[] dataToHash = new byte[] {
      (byte) (value        & 0xff),
      (byte)((value >> 8)  & 0xff),
      (byte)((value >> 16) & 0xff),
      (byte)((value >> 24) & 0xff)
    };
    return murmurHash3(nHashSeed, dataToHash);
  }

  /**
   * The practical limit of the size of a network-layer packet. If a packet is
   * larger than this, the library or application MAY drop it. This constant is
   * defined in this low-level class so that internal code can use it, but
   * applications should use the static API method
   * Face.getMaxNdnPacketSize() which is equivalent.
   */
  public static final int MAX_NDN_PACKET_SIZE = 8800;

  private enum Base64ConverterType {
    UNINITIALIZED, JAVAX, JAVA_UTIL, ANDROID, UNSUPPORTED
  }

  /**
   * If not already initialized, set base64Converter_ to the correct loaded
   * class and set base64ConverterType_ to the loaded type.
   * If base64ConverterType_ is UNINITIALIZED, set base64Converter_ to
   * the class for javax.xml.bind.DatatypeConverter and set
   * base64ConverterType_ to JAVAX.  Else try to set base64Converter_ to
   * the class for android.util.Base64 and set base64ConverterType_ to ANDROID.
   * If these fail, set base64ConverterType_ to UNSUPPORTED and throw an
   * UnsupportedOperationException from now on.
   */
  private static void
  establishBase64Converter()
  {
    if (base64ConverterType_ == Base64ConverterType.UNINITIALIZED) {
      try {
        // This is for Java 7, 8.
        base64Converter_ = Class.forName("javax.xml.bind.DatatypeConverter");
        base64ConverterType_ = Base64ConverterType.JAVAX;
        return;
      } catch (ClassNotFoundException ex) {}

      try {
        // This is for Java 9+.
        base64Converter_ = Class.forName("java.util.Base64");
        base64ConverterType_ = Base64ConverterType.JAVA_UTIL;
        return;
      } catch (ClassNotFoundException ex) {}

      try {
        base64Converter_ = Class.forName("android.util.Base64");
        base64ConverterType_ = Base64ConverterType.ANDROID;
        return;
      } catch (ClassNotFoundException ex) {}

      base64ConverterType_ = Base64ConverterType.UNSUPPORTED;
    }

   if (base64ConverterType_ == Base64ConverterType.UNSUPPORTED)
      throw new UnsupportedOperationException
        ("Common.establishBase64Converter: Cannot load a Base64 converter");
  }

  private static Base64ConverterType base64ConverterType_ = Base64ConverterType.UNINITIALIZED;
  private static Class base64Converter_ = null;
  private static Random randomNumberGenerator_;
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy