package gnu.crypto.assembly;
// ----------------------------------------------------------------------------
// $Id: Transformer.java,v 1.3 2005/10/06 04:24:13 rsdio Exp $
//
// Copyright (C) 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, 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; see the file COPYING. 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.pad.IPad;
import java.io.ByteArrayOutputStream;
import java.util.Map;
/**
* <p>A <code>Transformer</code> is an abstract representation of a two-way
* <i>transformation</i> that can be chained together with other instances of
* this type. Examples of such transformations in this library are:
* {@link Cascade} cipher, {@link gnu.crypto.pad.IPad} algorithm, and a
* ZLib-based deflater/inflater algorithm. A special implementation of a
* <code>Transformer</code> to close a chain is also provided.</p>
*
* <p>A <code>Transformer</code> is characterised by the followings:<p>
* <ul>
* <li>It can be chained to other instances, to form an {@link Assembly}.</li>
* <li>When configured in an {@link Assembly}, it can be set to apply its
* internal transformation on the input data stream before (pre-processing)
* or after (post-processing) passing the input data to the next element in
* the chain. Note that the same type <code>Transformer</code> can be used as
* either in pre-processing or a post-processing modes.</li>
* <li>A special transformer --<code>LoopbackTransformer</code>-- is used to
* close the chain.</li>
* <li>A useful type of <code>Transformer</code> --one we're interested in--
* has internal buffers. The distinction between a casual push (update)
* operation and the last one allows to correctly flush any intermediate
* bytes that may exist in those buffers.</li>
* </ul>
*
* <p>To allow wiring <code>Transformer</code> instances together, a
* <i>minimal-output-size</i> in bytes is necessary. The trivial case of a
* value of <code>1</code> for such attribute practically means that no output
* buffering, from the previous element, is needed --which is independant of
* buffering the input if the <code>Transformer</code> implementation itself is
* block-based.</p>
*
* @see CascadeTransformer
* @see PaddingTransformer
* @see DeflateTransformer
* @version $Revision: 1.3 $
*/
public abstract class Transformer {
// Constants and variables
// -------------------------------------------------------------------------
public static final String DIRECTION = "gnu.crypto.assembly.transformer.direction";
// public static final String MODE = "gnu.crypto.assembly.transformer.mode";
protected Direction wired;
protected Operation mode;
protected Transformer tail = null;
protected ByteArrayOutputStream inBuffer = new ByteArrayOutputStream(2048);
protected ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(2048);
// Constructor(s)
// -------------------------------------------------------------------------
/** Trivial protected constructor. */
protected Transformer() {
super();
this.wired = null;
}
// Class methods
// -------------------------------------------------------------------------
public static final Transformer getCascadeTransformer(Cascade cascade) {
return new CascadeTransformer(cascade);
}
public static final Transformer getPaddingTransformer(IPad padding) {
return new PaddingTransformer(padding);
}
public static final Transformer getDeflateTransformer() {
return new DeflateTransformer();
}
// Instance methods
// -------------------------------------------------------------------------
/**
* Sets the operational mode of this <code>Transformer</code>.
*
* @param mode the processing mode this <code>Transformer</code> is required
* to operate in.
* @throws IllegalStateException if this instance has already been assigned
* an operational mode.
*/
public void setMode(final Operation mode) {
if (this.mode != null) {
throw new IllegalStateException();
}
this.mode = mode;
}
/**
* Returns <code>true</code> if this <code>Transformer</code> was wired in
* pre-processing mode; <code>false</code> otherwise.
*
* @return <code>true</code> if this <code>Transformer</code> has been wired
* in pre-processing mode; <code>false</code> otherwise.
* @throws IllegalStateException if this instance has not yet been assigned
* an operational <i>type</i>.
*/
public boolean isPreProcessing() {
if (mode == null) {
throw new IllegalStateException();
}
return (mode == Operation.PRE_PROCESSING);
}
/**
* Returns <code>true</code> if this <code>Transformer</code> was wired in
* post-processing mode; <code>false</code> otherwise.
*
* @return <code>true</code> if this <code>Transformer</code> has been wired
* in post-processing mode; <code>false</code> otherwise.
* @throws IllegalStateException if this instance has not yet been assigned
* an operational <i>type</i>.
*/
public boolean isPostProcessing() {
return !isPreProcessing();
}
/**
* Initialises the <code>Transformer</code> for operation with specific
* characteristics.
*
* @param attributes a set of name-value pairs that describes the desired
* future behaviour of this instance.
* @throws IllegalStateException if the instance is already initialised.
*/
public void init(Map attributes) throws TransformerException {
if (wired != null) {
throw new IllegalStateException();
}
Direction flow = (Direction) attributes.get(DIRECTION);
if (flow == null) {
flow = Direction.FORWARD;
}
wired = flow;
inBuffer.reset();
outBuffer.reset();
tail.init(attributes); // initialise tail first
initDelegate(attributes); // initialise this instance
}
/**
* Returns the block-size of this <code>Transformer</code>. A value of
* <code>1</code> indicates that this instance is block-agnostic.
*
* @return the current minimal required block size.
*/
public int currentBlockSize() {
if (wired == null) {
throw new IllegalStateException();
}
return delegateBlockSize();
}
/**
* Resets the <code>Transformer</code> for re-initialisation and use with
* other characteristics. This method always succeeds.
*/
public void reset() {
resetDelegate();
wired = null;
inBuffer.reset();
outBuffer.reset();
tail.reset(); // reset tail last
}
/**
* Convenience method that calls the method with same name and three
* arguments, using a byte array of length <code>1</code> whose contents are
* the designated byte.
*
* @param b the byte to process.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
* @see #update(byte[], int, int)
*/
public byte[] update(byte b) throws TransformerException {
return update(new byte[] { b }, 0, 1);
}
/**
* Convenience method that calls the same method with three arguments. All
* bytes in <code>in</code>, starting from index position <code>0</code> are
* considered.
*
* @param in the input data bytes.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
* @see #update(byte[], int, int)
*/
public byte[] update(byte[] in) throws TransformerException {
return update(in, 0, in.length);
}
/**
* Processes a designated number of bytes from a given byte array.
*
* @param in the input data bytes.
* @param offset index of <code>in</code> from which to start considering
* data.
* @param length the count of bytes to process.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
*/
public byte[] update(byte[] in, int offset, int length)
throws TransformerException {
if (wired == null) {
throw new IllegalStateException();
}
byte[] result = (wired == Direction.FORWARD
? forwardUpdate(in, offset, length) : inverseUpdate(in, offset, length));
return result;
}
/**
* Convenience method that calls the same method with three arguments. A
* zero-long byte array is used.
*
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
* @see #lastUpdate(byte[], int, int)
*/
public byte[] lastUpdate() throws TransformerException {
byte[] result = (wired == Direction.FORWARD
? lastForwardUpdate() : lastInverseUpdate());
if (inBuffer.size() != 0) { // we still have some buffered bytes
throw new TransformerException("lastUpdate(): input buffer not empty");
}
return result;
}
/**
* Convenience method that calls the method with same name and three
* arguments, using a byte array of length <code>1</code> whose contents are
* the designated byte.
*
* @param b the byte to process.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
* @see #lastUpdate(byte[], int, int)
*/
public byte[] lastUpdate(byte b) throws TransformerException {
return lastUpdate(new byte[] { b }, 0, 1);
}
/**
* Convenience method that calls the same method with three arguments. All
* bytes in <code>in</code>, starting from index position <code>0</code> are
* considered.
*
* @param in the input data bytes.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
* @see #lastUpdate(byte[], int, int)
*/
public byte[] lastUpdate(byte[] in) throws TransformerException {
return lastUpdate(in, 0, in.length);
}
/**
* Processes a designated number of bytes from a given byte array and
* signals, at the same time, that this is the last <i>push</i> operation on
* this <code>Transformer</code>.
*
* @param in the input data bytes.
* @param offset index of <code>in</code> from which to start considering
* data.
* @param length the count of bytes to process.
* @return the result of transformation.
* @throws IllegalStateException if the instance is not initialised.
* @throws TransformerException if a transformation-related exception occurs
* during the operation.
*/
public byte[] lastUpdate(byte[] in, int offset, int length)
throws TransformerException {
byte[] result = update(in, offset, length);
byte[] rest = lastUpdate();
if (rest.length > 0) {
byte[] newResult = new byte[result.length + rest.length];
System.arraycopy(result, 0, newResult, 0, result.length);
System.arraycopy(rest, 0, newResult, result.length, rest.length);
result = newResult;
}
return result;
}
// helper methods ----------------------------------------------------------
private byte[] forwardUpdate(byte[] in, int off, int len) throws TransformerException {
return (isPreProcessing() ? preTransform(in, off, len) : postTransform(in, off, len));
}
private byte[] inverseUpdate(byte[] in, int off, int len) throws TransformerException {
return (isPreProcessing() ? postTransform(in, off, len) : preTransform(in, off, len));
}
private byte[] preTransform(byte[] in, int off, int len) throws TransformerException {
byte[] result = updateDelegate(in, off, len);
result = tail.update(result);
return result;
}
private byte[] postTransform(byte[] in, int off, int len) throws TransformerException {
byte[] result = tail.update(in, off, len);
result = updateDelegate(result, 0, result.length);
return result;
}
private byte[] lastForwardUpdate() throws TransformerException {
return (isPreProcessing() ? preLastTransform() : postLastTransform());
}
private byte[] lastInverseUpdate() throws TransformerException {
return (isPreProcessing() ? postLastTransform() : preLastTransform());
}
private byte[] preLastTransform() throws TransformerException {
byte[] result = lastUpdateDelegate();
result = tail.lastUpdate(result);
return result;
}
private byte[] postLastTransform() throws TransformerException {
byte[] result = tail.lastUpdate();
result = updateDelegate(result, 0, result.length);
byte[] rest = lastUpdateDelegate();
if (rest.length > 0) {
byte[] newResult = new byte[result.length + rest.length];
System.arraycopy(result, 0, newResult, 0, result.length);
System.arraycopy(rest, 0, newResult, result.length, rest.length);
result = newResult;
}
return result;
}
// abstract methods to be implemented by concrete subclasses ---------------
abstract void initDelegate(Map attributes) throws TransformerException;
abstract int delegateBlockSize();
abstract void resetDelegate();
abstract byte[] updateDelegate(byte[] in, int off, int len) throws TransformerException;
abstract byte[] lastUpdateDelegate() throws TransformerException;
}