/*
 * Copyright (c) 2019 the Eclipse Milo Authors
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */

package org.eclipse.milo.opcua.stack.core.util;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import org.eclipse.milo.opcua.stack.core.StatusCodes;
import org.eclipse.milo.opcua.stack.core.UaRuntimeException;


/**
 * <pre>
 * <b>P_SHA-1(secret, seed)</b> =
 * HMAC_SHA-1(secret, A(1) + seed) +
 * HMAC_SHA-1(secret, A(2) + seed) +
 * HMAC_SHA-1(secret, A(3) + seed) + ...
 * <i>Where + indicates concatenation.</i>
 * <br>
 * A() is defined as:
 * A(0) = seed
 * A(i) = HMAC_SHA-1(secret, A(i-1))
 * </pre>
 */
public class PShaUtil {

    public static byte[] createPSha1Key(byte[] secret, byte[] seed, int offset, int length) {
        return createKey("HmacSHA1", secret, seed, offset, length);
    }

    public static byte[] createPSha256Key(byte[] secret, byte[] seed, int offset, int length) {
        return createKey("HmacSHA256", secret, seed, offset, length);
    }

    private static byte[] createKey(String transformation, byte[] secret, byte[] seed, int offset, int length) {
        try {
            Mac mac = Mac.getInstance(transformation);

            byte[] tempBytes = hash(transformation, secret, seed, mac, offset + length);
            byte[] key = new byte[length];

            System.arraycopy(tempBytes, offset, key, 0, key.length);

            return key;
        } catch (Exception e) {
            throw new UaRuntimeException(StatusCodes.Bad_InternalError, e);
        }
    }

    private static byte[] hash(String transformation,
                               byte[] secret,
                               byte[] seed,
                               Mac mac,
                               int required) throws Exception {

        byte[] out = new byte[required];
        int offset = 0;
        int toCopy;
        byte[] a = seed;
        byte[] tmp;

        while (required > 0) {
            SecretKeySpec key = new SecretKeySpec(secret, transformation);
            mac.init(key);
            mac.update(a);
            a = mac.doFinal();
            mac.reset();
            mac.init(key);
            mac.update(a);
            mac.update(seed);
            tmp = mac.doFinal();
            toCopy = Math.min(required, tmp.length);
            System.arraycopy(tmp, 0, out, offset, toCopy);
            offset += toCopy;
            required -= toCopy;
        }

        return out;
    }

}
