KProvider.java

/*
 * Copyright 2020 Keve Müller
 *
 * 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 app.keve.ktlsh.spi;

import java.security.MessageDigestSpi;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.ProviderException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import app.keve.ktlsh.impl.TLSH;
import app.keve.ktlsh.impl.TLSHDigest4;
import app.keve.ktlsh.impl.TLSHDigest5;
import app.keve.ktlsh.impl.TLSHDigest6;
import app.keve.ktlsh.impl.TLSHDigest7;
import app.keve.ktlsh.impl.TLSHDigest8;

/**
 * The provider for the K set of algorithms to compute TLSH.
 * 
 * @author keve
 *
 */
public final class KProvider extends Provider {
    /** The canonical name of the provider. */
    public static final String NAME = "KProvider";
    /** The MessageDigest type string. */
    private static final String MESSAGE_DIGEST = "MessageDigest";
    /** The SPI class. */
    private static final String MESSAGE_DIGEST_SPI = "app.keve.ktlsh.spi.TLSHMessageDigestSpiK";
    /** Version UID. */
    private static final long serialVersionUID = 1L;

    /**
     * Initialise the K provider with the known algorithms.
     */
    public KProvider() {
        super(NAME, "1.0.1",
                "Implementation of the TLSH - Trend Locality Sensitive Hash MessageDigest using app.keve.ktlsh.");

        final int[] buckets = {TLSH.BUCKET_48, TLSH.BUCKET_128, TLSH.BUCKET_256};
        final int[] checksums = {1, 3};
        final int[] windowSizes = {TLSHDigest4.WINDOW_LENGTH, TLSHDigest5.WINDOW_LENGTH, TLSHDigest6.WINDOW_LENGTH,
                TLSHDigest7.WINDOW_LENGTH, TLSHDigest8.WINDOW_LENGTH};
        for (int bucket : buckets) {
            for (int checksum : checksums) {
                if (TLSH.BUCKET_48 == bucket && 3 == checksum) {
                    continue; // no 3 byte checksum with 48 buckets
                }
                for (int windowSize : windowSizes) {
                    final String fullName = String.format("TLSH-%d-%d/%d", bucket, checksum, windowSize);
                    if (TLSHDigest5.WINDOW_LENGTH == windowSize) {
                        final String shortName = String.format("TLSH-%d-%d", bucket, checksum);
                        if (1 == checksum && 128 == bucket) {
                            putService(new ProviderService(this, MESSAGE_DIGEST, fullName, MESSAGE_DIGEST_SPI, "TLSH",
                                    shortName));

                        } else {
                            putService(
                                    new ProviderService(this, MESSAGE_DIGEST, fullName, MESSAGE_DIGEST_SPI, shortName));
                        }
                    } else {
                        putService(new ProviderService(this, MESSAGE_DIGEST, fullName, MESSAGE_DIGEST_SPI));
                    }
                }
            }
        }
    }

    /**
     * Provider.Service for TLSH MesageDigest.
     * 
     * @author keve
     *
     */
    private static final class ProviderService extends Provider.Service {
        /** Regexp pattern for implemented algorithms. */
        private static final Pattern ALG_PATTERN = Pattern.compile("TLSH-(48|128|256)-(1|3)/([4-8])");

        ProviderService(final Provider p, final String type, final String algo, final String cn,
                final String... aliases) {
            super(p, type, algo, cn, List.of(aliases), null);
        }

        @Override
        public MessageDigestSpi newInstance(final Object ctrParamObj) throws NoSuchAlgorithmException {
            // We are only creating MessageDigest instances of this factory.
            assert MESSAGE_DIGEST.equals(getType());
//            try {
            final Matcher matcher = ALG_PATTERN.matcher(getAlgorithm());
            if (matcher.matches()) {
                final int bucketCount = Integer.valueOf(matcher.group(1));
                final int checksumCount = Integer.valueOf(matcher.group(2));
                final int windowSize = Integer.valueOf(matcher.group(3));
                return new TLSHMessageDigestSpiK(windowSize, bucketCount, checksumCount);
            }
//            } catch (final Exception ex) {
//                throw new NoSuchAlgorithmException("Error constructing " + type + " for " + algo + " using " + NAME,
//                        ex);
//            }
            // this line is very hard to reach, it would mean we have a mistake in
            // constructing fullName in KProvider constructor
            // or Java code calls us with bogus parameters.
            throw new ProviderException(
                    String.format("No impl for %s %s in %s", getType(), getAlgorithm(), getClass().getName()));
        }
    }
}