/**
 *  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.kerby.kerberos.kerb.crypto;

import org.apache.kerby.kerberos.kerb.KrbException;
import org.apache.kerby.kerberos.kerb.crypto.enc.EncryptProvider;
import org.apache.kerby.kerberos.kerb.crypto.enc.provider.Camellia128Provider;
import org.apache.kerby.kerberos.kerb.crypto.enc.provider.Camellia256Provider;
import org.apache.kerby.util.HexUtil;
import org.junit.jupiter.api.Test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Ref. camellia-test.c test in MIT krb5 project.
 */
public class CamelliaEncTest {

    private List<String> outputs = new ArrayList<>();

    private byte[] plain = new byte[16];
    private byte[] cipher = new byte[16];
    private EncryptProvider encProvider;

    private List<String> getExpectedLines() throws IOException {
        try (InputStream res = getClass().getResourceAsStream("/camellia-expect-vt.txt");
             BufferedReader br = new BufferedReader(new InputStreamReader(res))) {
            List<String> results = new ArrayList<>();
            String line;
            while ((line = br.readLine()) != null) {
                line = line.trim();
                if (!line.isEmpty()) {
                    results.add(line);
                }
            }
            return results;
        }
    }

    @Test
    public void testEnc() throws IOException, KrbException {
        List<String> expectedLines = getExpectedLines();

        testWith(16);
        outputs.add("==========");
        testWith(32);
        outputs.add("==========");

        List<String> newLines = expectedLines;
        assertThat(newLines).as("Comparing new lines with expected lines").isEqualTo(outputs);
    }

    private void testWith(int keySize) throws KrbException {
        outputs.add("KEYSIZE=" + (keySize * 8));

        encProvider = keySize == 16 ? new Camellia128Provider() : new Camellia256Provider();

        byte[] key = new byte[keySize];
        Arrays.fill(key, (byte) 0);
        hexDump("KEY", key);

        for (int i = 0; i < 16 * 8; ++i) {
            Arrays.fill(plain, (byte) 0);
            setBit(plain, i);
            outputs.add("I=" + (i + 1));
            hexDump("PT", plain);
            encWith(key);
            hexDump("CT", cipher);
        }
    }

    private void hexDump(String label, byte[] bytes) {
        String line = label + "=" + HexUtil.bytesToHex(bytes);
        outputs.add(line);
    }

    private static void setBit(byte[] bytes, int bitnum) {
        int bytenum = bitnum / 8;
        bitnum %= 8;
        // First bit is the high bit!
        bytes[bytenum] = (byte) (1 << (7 - bitnum));
    }

    private void encWith(byte[] key) throws KrbException {
        System.arraycopy(plain, 0, cipher, 0, plain.length);
        encProvider.encrypt(key, cipher);
    }
}
