package gnu.crypto.jce.params;

// --------------------------------------------------------------------------
// $Id: BlockCipherParameters.java,v 1.3 2005/10/06 04:24:15 rsdio Exp $
//
// Copyright (C) 2002, 2003 Free Software Foundation, Inc.
//
// This file is part of GNU Crypto.
//
// GNU Crypto 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 2 of the License, or (at
// your option) any later version.
//
// GNU Crypto 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, write to the
//
//    Free Software Foundation, Inc.,
//    51 Franklin Street, Fifth Floor,
//    Boston, MA  02110-1301
//    USA
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
//
// As a special exception, the copyright holders of this library give
// you permission to link this library with independent modules to
// produce an executable, regardless of the license terms of these
// independent modules, and to copy and distribute the resulting
// executable under terms of your choice, provided that you also meet,
// for each linked independent module, the terms and conditions of the
// license of that module.  An independent module is a module which is
// not derived from or based on this library.  If you modify this
// library, you may extend this exception to your version of the
// library, but you are not obligated to do so.  If you do not wish to
// do so, delete this exception statement from your version.
//
// --------------------------------------------------------------------------

import gnu.crypto.jce.spec.BlockCipherParameterSpec;

import java.io.IOException;
import java.math.BigInteger;

import java.security.AlgorithmParametersSpi;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidParameterSpecException;

/**
 * An implementation of algorithm parameters for the GNU Crypto block
 * ciphers. This encompasses the cipher's block size, its key size, and
 * an optional initialization vector (IV).
 *
 * @version $Revision: 1.3 $
 */
public class BlockCipherParameters extends AlgorithmParametersSpi {

   // Constants and variables.
   // -----------------------------------------------------------------------

   /**
    * The underlying block cipher specification.
    */
   protected BlockCipherParameterSpec cipherSpec;

   private static final String DEFAULT_FORMAT = "ASN.1";

   // Instance methods implementing AlgorithmParametersSpi.
   // -----------------------------------------------------------------------

   /**
    * Return these parameters encoded in ASN.1 (DER).
    *
    * <p>For GNU Crypto block ciphers we will define these parameters as
    *
    * <blockquote>
    * <pre>BlockCipherParameters ::= SEQUENCE {
    *   blockSize            INTEGER,
    *   keySize              INTEGER,
    *   initializationVector OCTET STRING OPTIONAL }</pre>
    * </blockquote>
    *
    * @return The parameters, encoded an an ASN.1 DER sequence.
    * @throws java.io.IOException If encoding these parameters fails.
    */
   protected byte[] engineGetEncoded() throws IOException {
      return engineGetEncoded(DEFAULT_FORMAT);
   }

   protected byte[] engineGetEncoded(String format) throws IOException {
      if (!format.equalsIgnoreCase(DEFAULT_FORMAT)
            && !format.equalsIgnoreCase("asn1")) {
         throw new IOException("unknown format \"" + format + "\"");
      }
      // This is probably a bad idea.
      /*
      int len = 12 + ((cipherSpec.getIV() != null)
         ? cipherSpec.getIV().length + 2 : 0);
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      out.write(0x30);
      out.write(len);
      out.write(0x02);
      out.write(4);
      out.write(cipherSpec.getBlockSize() >>> 24 & 0xff);
      out.write(cipherSpec.getBlockSize() >>> 16 & 0xff);
      out.write(cipherSpec.getBlockSize() >>>  8 & 0xff);
      out.write(cipherSpec.getBlockSize() & 0xff);
      out.write(0x02);
      out.write(4);
      out.write(cipherSpec.getKeySize() >>> 24 & 0xff);
      out.write(cipherSpec.getKeySize() >>> 16 & 0xff);
      out.write(cipherSpec.getKeySize() >>>  8 & 0xff);
      out.write(cipherSpec.getKeySize() & 0xff);
      if (cipherSpec.getIV() != null) {
         out.write(0x04);
         len = cipherSpec.getIV().length;
         out.write(len & 0xff);
         out.write(cipherSpec.getIV());
      }
      out.write(0); out.write(0);
      return out.toByteArray();*/
      DERWriter writer = new DERWriter();
      return writer.joinarrays(
         writer.writeBigInteger(BigInteger.valueOf(cipherSpec.getBlockSize())),
         writer.writeBigInteger(BigInteger.valueOf(cipherSpec.getKeySize())),
         (cipherSpec.getIV() != null) ?
         writer.writeBigInteger(new BigInteger(cipherSpec.getIV())) :
         new byte[0]);
   }

   protected void engineInit(AlgorithmParameterSpec spec)
   throws InvalidParameterSpecException
   {
      if (spec instanceof BlockCipherParameterSpec) {
         cipherSpec = (BlockCipherParameterSpec) spec;
      } else {
         throw new InvalidParameterSpecException();
      }
   }

   protected void engineInit(byte[] encoded, String format)
   throws IOException
   {
      if (!format.equalsIgnoreCase(DEFAULT_FORMAT) &&
          !format.equalsIgnoreCase("ASN1"))
      {
         throw new IOException("invalid format: only accepts ASN.1");
      }
      engineInit(encoded);
   }

   protected void engineInit(byte[] encoded) throws IOException {
      // This is probably an equally bad idea.
      /*if (encoded[0] != 0x30) {
         throw new IOException("malformed ASN.1 sequence");
      }
      if (encoded[2] != 0x02 || encoded[3] != 4) {
         throw new IOException("malformed ASN.1 sequence");
      }
      int blockSize = encoded[4] << 24 | encoded[5] << 16
                    | encoded[6] <<  8 | encoded[7];
      if (encoded[8] != 0x02 || encoded[9] != 4) {
         throw new IOException("malformed ASN.1 sequence");
      }
      int keySize = encoded[10] << 24 | encoded[11] << 16
                  | encoded[12] <<  8 | encoded[13];
      if (encoded[14] == 0x04) {
         int len = encoded[15] & 0xff;
         byte[] iv = new byte[len];
         System.arraycopy(encoded, 16, iv, 0, len);
         cipherSpec = new BlockCipherParameterSpec(iv, blockSize, keySize);
      } else if (encoded[14] == 0) {
         cipherSpec = new BlockCipherParameterSpec(blockSize, keySize);
      } else {
         throw new IOException("malformed ASN.1 sequence");
      }*/
      DERReader reader = new DERReader(encoded);
      int bs = reader.getBigInteger().intValue();
      int ks = reader.getBigInteger().intValue();
      byte[] iv = null;
      if (reader.hasMorePrimitives()) {
         iv = reader.getBigInteger().toByteArray();
      }
      cipherSpec = new BlockCipherParameterSpec(iv, bs, ks);
      System.out.println(cipherSpec);
   }

   protected AlgorithmParameterSpec engineGetParameterSpec(Class c)
   throws InvalidParameterSpecException
   {
      if (c.isInstance(cipherSpec)) {
         return cipherSpec;
      }
      throw new InvalidParameterSpecException();
   }

   protected String engineToString() {
      return cipherSpec.toString();
   }
}
