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

io.hotmoka.crypto.internal.AbstractHashingAlgorithmImpl Maven / Gradle / Ivy

/*
Copyright 2021 Fausto Spoto

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 io.hotmoka.crypto.internal;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import io.hotmoka.crypto.HashingAlgorithms;
import io.hotmoka.crypto.api.Hasher;
import io.hotmoka.crypto.api.HashingAlgorithm;

/**
 * A partial implementation of a hashing algorithm, that
 * provides a general implementation for partial hashing.
 * Subclasses might provide better implementations.
 */
public abstract class AbstractHashingAlgorithmImpl implements HashingAlgorithm {

	/**
	 * Hashes the given bytes.
	 * 
	 * @param bytes the bytes to hash
	 * @return the resulting hash; this must have length equals to {@linkplain #length()}
	 */
	private byte[] computeHash(byte[] bytes) {
		Objects.requireNonNull(bytes, "bytes cannot be null");
		return hash(bytes);
	}

	/**
	 * Hashes the given bytes.
	 * 
	 * @param bytes the bytes to hash; this is guaranteed to be non-{@code null}
	 * @return the resulting hash; this must have length equals to {@linkplain #length()}
	 */
	protected abstract byte[] hash(byte[] bytes);

	/**
	 * Hashes a portion of the given array of bytes, from
	 * {@code start} (inclusive) to {@code start + length}
	 * (exclusive) and computes the hash of that part only.
	 * 
	 * @param bytes the bytes to hash
	 * @param start the initial byte position to consider for hashing;
	 *              this must be a position inside {@code bytes}
	 * @param length the number of bytes (starting at {@code start})
	 *               that must be considered for hashing; this cannot be
	 *               negative and cannot lead to a position larger than {@code bytes.length}
	 * @return the hash; this must have length equals to {@linkplain #length()}
	 */
	private byte[] computeHash(byte[] bytes, int start, int length) {
		Objects.requireNonNull(bytes, "bytes cannot be null");

		if (start < 0)
			throw new IllegalArgumentException("start cannot be negative");

		if (length < 0)
			throw new IllegalArgumentException("length cannot be negative");

		if (start + length > bytes.length)
			throw new IllegalArgumentException("Trying to hash a portion larger than the array of bytes");

		return hash(bytes, start, length);
	}

	/**
	 * Hashes a portion of the given array of bytes, from
	 * {@code start} (inclusive) to {@code start + length}
	 * (exclusive) and computes the hash of that part only.
	 * 
	 * @param bytes the bytes to hash; this is guaranteed to be non-{@code null}
	 * @param start the initial byte position to consider for hashing;
	 *              this is guaranteed to be a position inside {@code bytes}
	 * @param length the number of bytes (starting at {@code start})
	 *               that must be considered for hashing; this is guaranteed to be
	 *               non-negative and no larger than {@code bytes.length}
	 * @return the hash; this must have length equals to {@linkplain #length()}
	 */
	protected byte[] hash(byte[] bytes, int start, int length) {
		var subarray = new byte[length];
		System.arraycopy(bytes, start, subarray, 0, length);

		return hash(subarray);
	}

	@Override
	public  Hasher getHasher(Function toBytes) {
		return new Hasher<>() {

			@Override
			public byte[] hash(T what) {
				return computeHash(toBytes.apply(what));
			}

			@Override
			public byte[] hash(T what, int start, int length) {
				return computeHash(toBytes.apply(what), start, length);
			}

			@Override
			public int length() {
				return AbstractHashingAlgorithmImpl.this.length();
			}
		};
	}

	/**
	 * Yields this same instance. Subclasses may redefine.
	 * 
	 * @return this same instance
	 */
	@Override
	public AbstractHashingAlgorithmImpl clone() {
		return this;
	}

	@Override
	public boolean equals(Object other) {
		return other != null && getClass() == other.getClass();
	}

	@Override
	public int hashCode() {
		return getClass().hashCode();
	}

	@Override
	public String getName() {
		return getClass().getSimpleName().toLowerCase();
	}

	@Override
	public String toString() {
		return getName();
	}

	/**
	 * Yields the hashing algorithm with the given name.
	 * It looks for a factory method with the given name and invokes it.
	 * 
	 * @param name the name of the algorithm, case-insensitive
	 * @return the algorithm
	 * @throws NoSuchAlgorithmException if the installation does not include the given algorithm
	 */
	public static HashingAlgorithm of(String name) throws NoSuchAlgorithmException {
		name = name.toLowerCase();

		try {
			// only sha256, shabal256 are currently found below
			Method method = HashingAlgorithms.class.getMethod(name);
			return (HashingAlgorithm) method.invoke(null);
		}
		catch (NoSuchMethodException | SecurityException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) {
			throw new NoSuchAlgorithmException("Unknown hashing algorithm " + name +
					" (alternatives are " + available().map(HashingAlgorithm::toString).collect(Collectors.joining(", ")) + ")", e);
		}
	}

	/**
	 * Yields the available hashing algorithms.
	 * 
	 * @return the available hashing algorithms
	 */
	private static Stream available() {
		return Stream.of(HashingAlgorithms.class.getDeclaredMethods())
			.filter(method -> Modifier.isPublic(method.getModifiers()))
			.filter(method -> Modifier.isStatic(method.getModifiers()))
			.filter(method -> method.getParameterCount() == 0)
			.filter(method -> method.getReturnType() == HashingAlgorithm.class)
			.map(AbstractHashingAlgorithmImpl::tryCreation)
			.flatMap(Optional::stream);
	}

	private final static Logger LOGGER = Logger.getLogger(HashingAlgorithms.class.getName());

	/**
	 * Tries to create a hashing algorithm by using the given supplier method.
	 * It yields an empty optional if the creation fails.
	 * 
	 * @param supplier the supplier method
	 * @return the hashing algorithm, if any
	 */
	private static Optional tryCreation(Method supplier) {
		try {
			return Optional.of((HashingAlgorithm) supplier.invoke(null));
		}
		catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
			LOGGER.warning("discarding hashing algorithm " + supplier.getName() + " since it could not be created: " + e.getMessage());
			return Optional.empty();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy