/*
 * Syncany, www.syncany.org
 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.syncany.crypto;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.syncany.crypto.specs.AesGcm128CipherSpec;
import org.syncany.crypto.specs.AesGcm256CipherSpec;
import org.syncany.crypto.specs.TwofishGcm128CipherSpec;
import org.syncany.crypto.specs.TwofishGcm256CipherSpec;

/**
 * Defines and identifies the application supported {@link CipherSpec}s.
 * 
 * <p>These cipher specs are used by the {@link MultiCipherOutputStream} to encrypt
 * data, and by the {@link MultiCipherInputStream} to decrypt data. The cipher spec
 * identifiers are used in the crypto format header to identify the crypto algorithms
 * used for encryption.
 * 
 * <p>The class defines a well defined (and developer-approved) set of allowed
 * cipher algorithms, modes and key sizes. The number of allowed ciphers is greatly
 * restricted to follow the application-specific security standards. Most prominently,
 * this includes:
 * 
 * <ul>
 *   <li>The block cipher mode must be authenticated (GCM, EAX, etc.). Unauthenticated
 *       modes are not supported and will be rejected by the {@link CipherSpec} sanity checks.
 *   <li>The block cipher mode must require an initialization vector (IV). Modes that do 
 *       not require an IV (e.g. ECB) will be rejected by the {@link CipherSpec} sanity checks.
 * </ul>
 * 
 * @author Philipp C. Heckel <philipp.heckel@gmail.com>
 */
public class CipherSpecs {
	private static final Map<Integer, CipherSpec> cipherSpecs = new TreeMap<Integer, CipherSpec>();

	/*
	 * WARNING: The cipher spec identifiers are written to the MultiCipherOutputStream and read by the MultiCipherInputStream. The identifiers MUST
	 * NOT be changed, because this will make decryption of already encrypted data impossible!
	 */
	public static final int AES_128_GCM = 0x01;
	public static final int TWOFISH_128_GCM = 0x02;
	public static final int AES_256_GCM = 0x03;
	public static final int TWOFISH_256_GCM = 0x04;

	public static final int[] DEFAULT_CIPHER_SPECS = new int[] { CipherSpecs.AES_128_GCM };

	static {
		CipherSpec[] tmpCipherSpecs = new CipherSpec[] {
				// Standard
				new AesGcm128CipherSpec(),
				new TwofishGcm128CipherSpec(),

				// Unlimited crypto
				new AesGcm256CipherSpec(),
				new TwofishGcm256CipherSpec() 
			};

		for (CipherSpec cipherSpec : tmpCipherSpecs) {
			registerCipherSpec(cipherSpec.getId(), cipherSpec);
		}
	}

	/**
	 * Returns a list of available/registered {@link CipherSpec}s. Refer to the 
	 * {@link CipherSpecs class description} for a more detailed explanation.
	 */
	public static Map<Integer, CipherSpec> getAvailableCipherSpecs() {
		return cipherSpecs;
	}
	
	/**
	 * Returns the default {@link CipherSpec}s used by the application.
	 */
	public static List<CipherSpec> getDefaultCipherSpecs() {
		List<CipherSpec> cipherSpecs = new ArrayList<CipherSpec>();
		
		for (int cipherSpecId : DEFAULT_CIPHER_SPECS) { 
			cipherSpecs.add(getCipherSpec(cipherSpecId));
		}	
		
		return cipherSpecs;
	}

	/**
	 * Retrieves an available/registered {@link CipherSpec} using the cipher spec identifier
	 * defined in this class.
	 * 
	 * @param id Identifier of the cipher spec
	 * @return A cipher spec, or <tt>null</tt> if no cipher spec with this identifier is registered
	 */
	public static CipherSpec getCipherSpec(int id) {
		return cipherSpecs.get(id);
	}

	/**
	 * Register a new cipher spec.
	 * 
	 * <p>Note: Registering a cipher spec locally does not make it available on all clients. Unless
	 * a cipher spec is registered before a client tries to decrypt data using the {@link MultiCipherInputStream},
	 * the decryption process will fail. 
	 * 
	 * @param id Identifier of the cipher spec
	 */
	public static void registerCipherSpec(int id, CipherSpec cipherSpec) {
		cipherSpecs.put(id, cipherSpec);
	}
}
