/*
 * Copyright 2017 ZhangJiupeng
 *
 * 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 cc.agentx.security;

import cc.agentx.util.KeyHelper;
import org.bouncycastle.crypto.StreamBlockCipher;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CFBBlockCipher;
import org.bouncycastle.crypto.modes.OFBBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;

import javax.crypto.spec.SecretKeySpec;

public class AesCipher extends Cipher {
    // encryption mode
    public static final int AES_128_CFB = 16;
    public static final int AES_192_CFB = 24;
    public static final int AES_256_CFB = 32;
    public static final int AES_128_OFB = -16;
    public static final int AES_192_OFB = -24;
    public static final int AES_256_OFB = -32;
    private final int keyLength;
    private final StreamBlockCipher cipher;

    /**
     * <b>Notice: </b><br>
     * 1. the <code>AESFastEngine</code> was replaced by <code>AESEngine</code> now.<br>
     * 2. in <code>new CFBBlockCipher(engine, <b>16</b> * 8);</code> the IV length (16) is
     * reference to the shadowsocks's design.
     *
     * @see <a href="https://www.bouncycastle.org/releasenotes.html">
     * https://www.bouncycastle.org/releasenotes.html</a>#CVE-2016-1000339<br>
     * <a href="https://shadowsocks.org/en/spec/cipher.html">
     * https://shadowsocks.org/en/spec/cipher.html</a>#Cipher
     */
    public AesCipher(String password, int mode) {
        key = new SecretKeySpec(password.getBytes(), "AES");
        keyLength = Math.abs(mode);
        AESEngine engine = new AESEngine();
        if (mode > 0) {
            cipher = new CFBBlockCipher(engine, 16 * 8);
        } else {
            cipher = new OFBBlockCipher(engine, 16 * 8);
        }
    }

    public static boolean isValidMode(int mode) {
        int modeAbs = Math.abs(mode);
        return modeAbs == 16 || modeAbs == 24 || modeAbs == 32;
    }

    @Override
    protected void _init(boolean isEncrypt, byte[] iv) {
        String keyStr = new String(key.getEncoded());
        ParametersWithIV params = new ParametersWithIV(
                new KeyParameter(KeyHelper.generateKeyDigest(keyLength, keyStr)), iv
        );
        cipher.init(isEncrypt, params);
    }

    @Override
    protected byte[] _encrypt(final byte[] originData) {
        byte[] encryptedData = new byte[originData.length];
        cipher.processBytes(originData, 0, originData.length, encryptedData, 0);
        return encryptedData;
    }

    @Override
    protected byte[] _decrypt(final byte[] encryptedData) {
        byte[] originData = new byte[encryptedData.length];
        cipher.processBytes(encryptedData, 0, encryptedData.length, originData, 0);
        return originData;
    }

    @Override
    public int getIVLength() {
        return 16;
    }
}