MathType

сряда, 24 февруари 2016 г.

Емулатор на немска шифровъчна машина Енигма

Част III. Програмният код

Настоящата статия е разделена в няколко публикации на блога:
Част I. Принцип на работа на Енигма машина
Част II. Детайли за роторите и математически модел на машината
Част III. Програмният код
Последна редакция: 26.02.2016г.
Кодът не е тестван достатъчно за грешки.

След подробното излагане на принципите и особеностите на Енигма машините, сме готови да преминем към написването на програмния код. Започваме с няколко "сервизни" класове и интерфейси.

Alphabet е клас, който съдържа константата ALPHABET със стойност "ABCDEFG...". Ролята на ALPHABET е от нея да се изчитат позициите на буквите в азбуката, които се използват от Енигма роторите.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public final class Alphabet {
 /**
  * Constant string with all capital Latin letters of the alphabet in alphabetical 
  * order. It's needed to establish the order of letters. A is 0, B is 1 etc. 
  * and we get it by checking the position of each letter the string.
  */ 
 public final static String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
}

Интерфейсът EnigmaComponent се реализира от класове, които емулират елемент на Енигма машината, внасящ пермутация в математическия ѝ модел.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public interface EnigmaComponent {
 public char permuteChar (char input);
 public char invertChar (char input);
}

Имаме и специфично инстанцирано изключение.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public class InvalidEnigmaConfiguration extends IllegalArgumentException {
 public  InvalidEnigmaConfiguration(String message) {
  super(message);
 }
}

EnigmaPermutation инициализира пермутация на буквите от латинската азбука в нотация на Коши и предоставя методи за прилагането и върху букви от латинската азбука.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

/**
 * Encapsulates the permutation functionality needed by Enigma. 
 * <p>
 * Enigma machines permute the letters of Latin alphabet and are 
 * case-insensitive. So this class facilitates working
 * with permutations of capital letters of the Latin alphabet. 
 * 
 * @author Angelin Lalev
 *
 */

public class EnigmaPermutation { 

 protected String permutation;

 
 /**
  * No-argument constructor, which creates neutral permutation of the captial latin letters
  * (Each letter is sent to itself).
  */
 public EnigmaPermutation() {
  permutation = Alphabet.ALPHABET;
 }
 
 /**
  * Creates user-specified permutation.   
  * @param permutation gives the permutation in Cauchy notation. More specifically, 
  * it gives the second line of the "table". In example the value for @param permutation of 
  * "ZABCDEFGHIJKLMNOPQRSTUVWXY" sets the permutation so, that it sends 
  * A to Z, B to A, C to B etc.
  */
 
 public EnigmaPermutation(String permutation) { 
  for (int i=0; i<Alphabet.ALPHABET.length(); i++) {
   if (!permutation.contains(Character.toString(Alphabet.ALPHABET.charAt(i)))) {
    throw new InvalidEnigmaConfiguration("Invalid Enigma permutation");
   }
  }
  
  if (Alphabet.ALPHABET.length()!=permutation.length()) {
   throw new InvalidEnigmaConfiguration("Invalid Enigma permutation");
  }
  this.permutation = permutation;
 }
 
 
 /**
  * Applies the permutation to a letter
  * 
  * @param input contains the letter to be permuted
  * @return returns the resulting letter
  */
 public char permute(char input) {
  return permutation.charAt(Alphabet.ALPHABET.indexOf(input));
 }
 
 /**
  * Applies the <em>inverse</em> permutation to a letter
  * 
  * @param input contains the letter to be permuted
  * @return returns the resulting letter
  */ 
 public char invert(char input) {
  return Alphabet.ALPHABET.charAt(permutation.indexOf(input)); 
 }
}

EnigmaRhoPermutation описва пермутации, които отговарят на въртенето на ротора, както и на отместванията на азбучните пръстени на роторите. Тези пермутации всъщност са циклична група с генератор "BCDEF...YZA".

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

/**
 * Creates a member of the group of permutations that are needed to represent the movement of an 
 * Enigma rotor. This group is cyclic. All of the permutations in the group are compositions of a basic 
 * "generator" permutation with itself. The basic permutation sends A to B, 
 * B to C, C to D etc. it can be "composed" several times with itself, giving any other member 
 * of this group.  
 * 
 * @author Angelin Lalev
 *
 */

public class EnigmaRhoPermutation extends EnigmaPermutation {

 protected int times;
 
 /**
  * No-argument constructor that creates an instance that represents neutral permutation of the 
  * capital Latin letters (Each letter is sent to itself).
  */

 public EnigmaRhoPermutation() {
  super();
  times=0;
 }


 /**
  * Creates an instance, representing the n-th member of the permutation group. 
  */
 
 public EnigmaRhoPermutation(int times) {
  super();
  advance(times);
 }

 /**
  * Applies the basic permutation to the current one n times to get the n-th next permutation in the 
  * group. The result becomes the new current permutation, which is represented by the instance. 
  */
 
 public void advance(int times) {
  String newperm = permutation; 
  int tmptimes = times % permutation.length();
  
  for (int i=0; i<tmptimes; i++) {
   newperm = permutation.substring(1, permutation.length());
   permutation = newperm+permutation.substring(0,1);
  }
  
  this.times = (tmptimes+times)%permutation.length();
 }
}

Основният клас, който инкапсулира функционалността на Енигма машината, разглеждана като формула, е EnigmaMachine.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

import java.util.ArrayList;

/**
 * Class that encapsulates the functionality of the Enigma machine.
 * 
 * @author Angelin Lalev
 *
 */
public class EnigmaMachine {
 public static final int MIN_ROTOR_COUNT = 3;
 
 protected ArrayList<EnigmaRotor> rotors;
 protected EnigmaReflector reflector;
 protected EnigmaSteckerBoard steckerboard;
 protected EnigmaETW etw;
 
 protected boolean valid;
 
 /**
  * Constructs classical EnigmaMachine
  * @param rotor1 - The rightmost rotor
  * @param rotor2 - The middle rotor
  * @param rotor3 - The leftmost rotor
  * @param rotor4 - Eventually, a forth rotor, to the left of the current leftmost rotor
  * @param reflector - The reflector of the machine
  * @param steckerboard - The steckerboard of the machine
  * @param etw - The ETW ring of the machine
  */
 public EnigmaMachine(EnigmaRotor rotor1, EnigmaRotor rotor2, EnigmaRotor rotor3, 
     EnigmaRotor rotor4, EnigmaReflector reflector, EnigmaSteckerBoard steckerboard,
     EnigmaETW etw) {
  if ((rotor1 == null) || (rotor2==null) || (rotor3==null)) {
   throw new InvalidEnigmaConfiguration("At least rotors 1, 2 and 3 must be intialized");
  } 
  
  if (reflector==null) {
   throw new InvalidEnigmaConfiguration("Reflector of the Enigma machine must be initialized.");
  }

  if (steckerboard==null) {
   this.steckerboard = new EnigmaSteckerBoard();
  }
  
  if (etw == null) {
   this.etw = new EnigmaETW();
  }

  rotors = new ArrayList<EnigmaRotor>(MIN_ROTOR_COUNT);
  
  this.rotors.add(rotor1);
  this.rotors.add(rotor2);
  this.rotors.add(rotor3);
  if (rotor4!=null) {
   this.rotors.add(rotor4);
  }
  this.reflector = reflector;
  this.steckerboard = steckerboard;

  this.valid = true;
 }
 
 public EnigmaMachine() {
  rotors = new ArrayList<EnigmaRotor>(MIN_ROTOR_COUNT);
  this.valid = false;
 }
 
 public void addRotor(EnigmaRotor rotor) {
  if (rotor!=null) {
   rotors.add(rotor);
  }
 }
 
 public void addReflector(EnigmaReflector reflector) {
  if (reflector!=null) {
   this.reflector = reflector;
  }
 }
 
 public void addSteckerBoard(EnigmaSteckerBoard steckerboard) {
  if (steckerboard!=null) {
   this.steckerboard = steckerboard;
  }
 }
 
 public void addETW(EnigmaETW etw) {
  if (etw!=null) {
   this.etw = etw;
  }
 }

 public void validate() {
  for (int i=0; i<MIN_ROTOR_COUNT; i++) {
   try {
    if (rotors.get(i)==null) {
     throw new InvalidEnigmaConfiguration("At least "+ MIN_ROTOR_COUNT + "rotors must be present in an Enigma machine");
    }
   } catch (IndexOutOfBoundsException e) {
    throw new InvalidEnigmaConfiguration("At least " + MIN_ROTOR_COUNT + "rotors must be present in an Enigma machine");
   }
  }

  if (reflector==null) {
   throw new InvalidEnigmaConfiguration("No reflector added to machine");
  }
  
  
  if (steckerboard==null) {
   throw new InvalidEnigmaConfiguration("No steckerboard added to machine");
  }
  
  if (etw==null) {
   throw new InvalidEnigmaConfiguration("No etw added to machine");
  }
  
  valid = true;
 }
 
 /**
  * Moves the rotors according to notches and takes into account 
  * the double step phenomena.
  *  
  */
 public void stepRotors() {
  if (!valid) {
   throw new InvalidEnigmaConfiguration("Enigma configuration must be validated before being used!");
  }

  
  rotors.get(0).markForMovement();
  
  for (int i=1; i<rotors.size(); i++) {
   if (rotors.get(i-1).onNotch()) {
     rotors.get(i).markForMovement();
     rotors.get(i-1).markForMovement();
   }
  }
  
  for (int i=0; i<rotors.size(); i++) {
   rotors.get(i).move();
  }
 }
 
 /**
  * Returns a string that represent the current rotor poisitions
  * of each rotor. The leftmost char is the leftmost rotor.
  *  
  * @return
  */
 public String getRotorPositions() {
  String result="";
  for (int i=rotors.size()-1; i>=0; i--) {
   result=result+rotors.get(i).getPositionChar();
  }
  return result;
 }
 
 public char encodeChar(char input) {
  char output;

  if (!valid) {
   throw new InvalidEnigmaConfiguration("Enigma configuration must be validated before being used!");
  }
  
  stepRotors();

  output = steckerboard.permuteChar(input);

  output = etw.permuteChar(output);
  
  for (int i=0; i<rotors.size(); i++) {
   output = rotors.get(i).permuteChar(output);
  }

  output = reflector.permuteChar(output);
  
  for (int i=rotors.size()-1; i>=0; i--) {
   output = rotors.get(i).invertChar(output);
  }

  output = etw.invertChar(output);
  
  output = steckerboard.invertChar(output);

  return output;
 }
 
 public String encodeMessage(String message) {
  String result="";
  for (int i=0; i<message.length(); i++) {
   result = result+encodeChar(message.charAt(i));
  }
  return result;
 }
}

EnigmaMachine притежава списък с ротори, които са реализирани като инстанции на EnigmaRotor.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

/**
 * Emulates an Engima rotor. 
 * <p>
 * It does so by creating a EnigmaPermutation that corresponds to 
 * the rotor wirings. Additionally, it creates two EnigmaRhoPermutations
 * that correspond respectively to ring offset and rotor position.
 * One that has the rotor position can "advance" to simulate the rotor 
 * movement.   
 * 
 * @author Angelin Lalev
 *
 */

public class EnigmaRotor implements EnigmaComponent {

 public static final String[][] ROTOR_CONFIGURATIONS = {{"I","EKMFLGDQVZNTOWYHXUSPAIBRCJ", "Q"},
   {"II", "AJDKSIRUXBLHWTMCQGZNPYFVOE", "E"}, 
   {"III", "BDFHJLCPRTXVZNYEIWGAKMUSQO", "V"},
   {"IV", "ESOVPZJAYQUIRHXLNFTGKDCMWB", "J"},
   {"V", "VZBRGITYUPSDNHLXAWMJQOFECK", "Z"},
   {"VI", "JPGVOUMFYQBENHZRDKASXLICTW", "ZM"},
   {"VII", "NZJHGRCXMYSWBOUFAIVLPEKQDT", "ZM"},
   {"VIII", "FKQHTLXOCBJSPDZRAMEWNIUYGV", "ZM"},
  };

// private String type;
 private String notches;
 private EnigmaRhoPermutation positionPerm;
 private EnigmaRhoPermutation ringPerm;
 private EnigmaPermutation rotorPerm;
 private char positionChar;
// private char ringChar;
 private boolean markedForMovement;
 
 public char permuteChar(char input) {

  return positionPerm.invert(
     ringPerm.permute(
       rotorPerm.permute(
         ringPerm.invert(
           positionPerm.permute(input)
    ))));
 }
 
 public char invertChar(char input) {
  return positionPerm.invert(
     ringPerm.permute(
       rotorPerm.invert(
         ringPerm.invert(
           positionPerm.permute(input)
    ))));
 } 
 
 public void makeStep() {
  positionPerm.advance(1);
  if (positionChar=='Z') {
   positionChar='A';
  } else {
   positionChar++;
  }
 }

 public void markForMovement() {
  markedForMovement = true;
 }
 
 public void move() {
  if (markedForMovement) {
   makeStep();
  };
  markedForMovement = false;
 }
 
 public boolean onNotch() {
  return notches.contains(Character.toString(positionChar));
 }
 
 /**
  * Returns current position on the alphabetic ring of the rotor.
  */
 public char getPositionChar() {
  return positionChar;
 }
 
 /**
  * Create EnigmaRotor with historically correct configuration 
  * 
  * @param rotorType - the type of the rotor. Military Enigma machines had eight standard rotor configurations.
  * @param ringOffset - the offset of the alphabet ring
  * @param rotorPosition - the initial position of the rotor
  */
 
 public EnigmaRotor(String rotorType, char ringOffset, char rotorPosition) {

//  this.ringChar = ringOffset;
  this.positionChar = rotorPosition;
  
  this.ringPerm = new EnigmaRhoPermutation(Alphabet.ALPHABET.indexOf(ringOffset));
  this.positionPerm = new EnigmaRhoPermutation(Alphabet.ALPHABET.indexOf(rotorPosition)); 
  this.markedForMovement = false;
  
  for (int c=0; c<ROTOR_CONFIGURATIONS.length; c++) {
   if (ROTOR_CONFIGURATIONS[c][0].equals(rotorType)) {
//    this.type=ROTOR_CONFIGURATIONS[c][0];
    this.rotorPerm = new EnigmaPermutation(ROTOR_CONFIGURATIONS[c][1]);
    this.notches=ROTOR_CONFIGURATIONS[c][2];
    return;
   }
  }
  throw new InvalidEnigmaConfiguration("Rotor "+rotorType+" is not in the list of known rotors.");
 }
 
 /**
  * Construct custom Enigma rotor
  * @param permutation - gives the permutation of the alphabet that this rotor facilitates.
  * @param rotorType - readable user-specified description of the rotor.
  * @param ringOffset - offset of the alphabet ring of the rotor
  * @param rotorPosition - the initial position of the rotor 
  * @param notches - a String that describes all the notches of the ring with the corresponding letter names on the ring.
  */

 public EnigmaRotor(String permutation, String rotorType, char ringOffset, char rotorPosition, String notches) {
  this.ringPerm = new EnigmaRhoPermutation(Alphabet.ALPHABET.indexOf(ringOffset));
  this.positionPerm = new EnigmaRhoPermutation(Alphabet.ALPHABET.indexOf(rotorPosition));
  this.markedForMovement = false;
  
  //this.type = rotorType;
  this.rotorPerm = new EnigmaPermutation(permutation);
  this.notches = notches;
 }
}

... и един рефлектор, който е реализиран от класа EnigmaReflector.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public class EnigmaReflector implements EnigmaComponent  {
 public static final String[][] REFLECTOR_CONFIGURATIONS = {{"A","EJMZALYXVBWFCRQUONTSPIKHGD"},
   {"B", "YRUHQSLDPXNGOKMIEBFZCWVJAT"}, 
   {"C", "FVPJIAOYEDRZXWGCTKUQSBNMHL"},
   {"B Thin", "ENKQAUYWJICOPBLMDXZVFTHRGS"},
   {"C Thin", "RDOBJNTKVEHMLFCWZAXGYIPSUQ"},
 };

// private String type;
 protected EnigmaPermutation reflectorPerm;
 
 /**
  * Creates a historically accurate Enigma reflector 
  * 
  * @param reflectorType
  */

 public EnigmaReflector(String reflectorType) {
  for (int c=0; c<REFLECTOR_CONFIGURATIONS.length; c++) {
   if (reflectorType.equals(REFLECTOR_CONFIGURATIONS[c][0])) {
//    type=REFLECTOR_CONFIGURATIONS[c][0];
    reflectorPerm = new EnigmaPermutation(REFLECTOR_CONFIGURATIONS[c][1]);
    return;
   }
  }
  
  throw new InvalidEnigmaConfiguration("Reflector "+reflectorType+" is not in the list of known reflectors.");
 }

 public EnigmaReflector(String permutation, String reflectorType) {
  reflectorPerm = new EnigmaPermutation(permutation);
  
  for (int i=0; i<permutation.length(); i++) {
   if (reflectorPerm.permute(reflectorPerm.permute(Alphabet.ALPHABET.charAt(i)))!=Alphabet.ALPHABET.charAt(i)) {
    throw new InvalidEnigmaConfiguration("Reflector permutation must be inverse to itself.");
   }
  }
 }
 
 
 public char permuteChar(char input) {
  return reflectorPerm.permute(input);
 }
 
 public char invertChar(char input) {
  return reflectorPerm.invert(input);
 }
 
};

EnigmaSteckerBoard реализира щекерно табло.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public class EnigmaSteckerBoard implements EnigmaComponent {
 protected EnigmaPermutation steckerPerm;
 
 public EnigmaSteckerBoard(String permutation) {
  this.steckerPerm = new EnigmaPermutation(permutation);
 }
 
 public EnigmaSteckerBoard() {
  this.steckerPerm = new EnigmaPermutation(Alphabet.ALPHABET);
 }
 
 public char permuteChar(char input) {
  return steckerPerm.permute(input);
 } 
 
 public char invertChar(char input) {
  return steckerPerm.invert(input);
 }  
}

А EnigmaETW реализира пермутацията на входното колело.

/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */

package code.lalev.enigmasim;

public class EnigmaETW implements EnigmaComponent {
 protected EnigmaPermutation etwPerm;
  
 /**
  * Creates custom ETW 
  * @param permutation - String that describe 
  */
 public EnigmaETW (String permutation) {
  this.etwPerm = new EnigmaPermutation(permutation);
 }
 
 /**
  * Constructs an ETW that does not permute anything. Such ETW is typical 
  * for military Enigmas.
  */
 public EnigmaETW () {
  this.etwPerm = new EnigmaPermutation(Alphabet.ALPHABET);
 } 
 
 public char permuteChar(char input) {
  return etwPerm.permute(input);
 } 
  
 public char invertChar(char input) {
  return etwPerm.invert(input);
 }  
}
И за финал, елементарна тестова програма, която проверява коректността на различни параметри на кода.
/*
 * This file is authored by Angelin Lalev and may be
 * distributed under GPLv3 license which may be 
 * obtained from:
 * http://www.gnu.org/licenses/gpl-3.0.en.html
 */


package code.lalev.engimasim.unittests;

import code.lalev.enigmasim.*;

public class EnigmaTest {

 public static void main(String[] args) {
  System.out.println("Unit tests for code.lalev.enigmasim package");
  System.out.println("===========================================");

  System.out.print("Constructing I,II,III,B Enigma with no steckerboard or etw ... ");
  EnigmaMachine enigma = new EnigmaMachine();
  EnigmaRotor rotor = new EnigmaRotor("I",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("III", 'A', 'A');
  enigma.addRotor(rotor);
  EnigmaReflector reflector = new EnigmaReflector("B");
  enigma.addReflector(reflector);
  EnigmaETW etw = new EnigmaETW();
  enigma.addETW(etw);
  EnigmaSteckerBoard board = new EnigmaSteckerBoard();
  enigma.addSteckerBoard(board);
  enigma.validate();
  System.out.println("SUCCESS");
  
  System.out.print("Constructing IV,V,VI,VII,VII,C Enigma with (AB)(CD)(EF)(GH) steckerboard and no etw ... ");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("IV",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("V", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("VI", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("VII", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("VIII", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard("BADCFEHGIJKLMNOPQRSTUVWXYZ");
  enigma.addSteckerBoard(board);
  enigma.validate();
  System.out.println("SUCCESS");

  System.out.print("Catching invalid Enigma with 2 rotors... ");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("IV",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("V", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard("BADCFEHGIJKLMNOPQRSTUVWXYZ");
  enigma.addSteckerBoard(board);
  try {
   enigma.validate();
   System.out.println("FAIL");
  } catch (Exception e) {
   System.out.println("SUCCESS");  
  }
  
  System.out.println("Rotor movement and double stepping tests ...IN PROGRESS");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("I",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("III", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard();
  enigma.addSteckerBoard(board);
  enigma.validate();

  System.out.println("Test 1");
  String desiredoutput = "AAA AAB AAC AAD AAE AAF AAG AAH AAI AAJ ";
  String realoutput=""; 
  System.out.println("Correct: "+desiredoutput);
  for (int i=0; i<10; i++) {
   realoutput=realoutput+enigma.getRotorPositions()+" ";
   enigma.stepRotors();
  }
  System.out.println("Result: "+realoutput);
  if (desiredoutput.equals(realoutput)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }
  
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("I",'A', 'O');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("III", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard();
  enigma.addSteckerBoard(board);
  enigma.validate();

  System.out.println("Test 2");
  desiredoutput = "AAO AAP AAQ ABR ABS ABT ";
  realoutput=""; 
  System.out.println("Correct: "+desiredoutput);
  for (int i=0; i<6; i++) {
   realoutput=realoutput+enigma.getRotorPositions()+" ";
   enigma.stepRotors();
  }
  System.out.println("Result: "+realoutput); 
  if (desiredoutput.equals(realoutput)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }
  
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("III",'Z', 'U');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'P', 'D');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("I", 'Q', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard();
  enigma.addSteckerBoard(board);
  enigma.validate();

  System.out.println("Test 3 (Double step)");
  desiredoutput = "ADU ADV AEW BFX BFY ";
  realoutput=""; 
  System.out.println("Correct: "+desiredoutput);
  for (int i=0; i<5; i++) {
   realoutput=realoutput+enigma.getRotorPositions()+" ";
   enigma.stepRotors();
  }
  System.out.println("Result: "+realoutput); 
  if (desiredoutput.equals(realoutput)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }  
  
  
  System.out.println("Testing encryption ... IN PROGRESS"); 
  System.out.println("Test 1. Encrypt: Rotors I, II, III, GrundStellung:AAA Ringstellung:AAA, Refl. B, no etw, Steckerboard:(AB)(CD)");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("I",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("III", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("B");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard("BADCEFGHIJKLMNOPQRSTUVWXYZ");
  enigma.addSteckerBoard(board);
  enigma.validate();  
  
  String plaintext = "AAAAAAAA";
  String ciphertext = "WUPGNWOJ";
  String result = enigma.encodeMessage(plaintext);
  System.out.println("Plaintext:"+plaintext);
  System.out.println("Ciphertext:"+ciphertext);
  System.out.println("Result:"+result);
  if (result.equals(ciphertext)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }

  System.out.println("Test 2. Decrypt: Rotors I, II, III, GrundStellung:AAA Ringstellung:AAA, Refl. B, no etw, Steckerboard:(AB)(CD)");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("I",'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("II", 'A', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("III", 'A', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("B");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard("BADCEFGHIJKLMNOPQRSTUVWXYZ");
  enigma.addSteckerBoard(board);
  enigma.validate();  
  
  plaintext = "AAAAAAAA";
  ciphertext = "WUPGNWOJ";
  result = enigma.encodeMessage(ciphertext);
  System.out.println("Plaintext:"+plaintext);
  System.out.println("Ciphertext:"+ciphertext);
  System.out.println("Result:"+result);
  if (result.equals(plaintext)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }  
  
  System.out.println("Test 3. Encrypt: Rotors IV, V, VI, GrundStellung:AAA Ringstellung:BBB, Refl.C, no etw, Steckerboard:(AB)(CD)");
  enigma = new EnigmaMachine();
  rotor = new EnigmaRotor("VI",'B', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("V", 'B', 'A');
  enigma.addRotor(rotor);
  rotor = new EnigmaRotor("IV", 'B', 'A');
  enigma.addRotor(rotor);
  reflector = new EnigmaReflector("C");
  enigma.addReflector(reflector);
  etw = new EnigmaETW();
  enigma.addETW(etw);
  board = new EnigmaSteckerBoard("BADCEFGHIJKLMNOPQRSTUVWXYZ");
  enigma.addSteckerBoard(board);
  enigma.validate();  
 
  plaintext = "AAAAAAAA";
  ciphertext = "TDKWFFQE";
  result = enigma.encodeMessage(plaintext);
  System.out.println("Plaintext:"+plaintext);
  System.out.println("Ciphertext:"+ciphertext);
  System.out.println("Result:"+result);
  if (result.equals(ciphertext)) {
   System.out.println("SUCCESS");
  } else {
   System.out.println("FAILURE");
  }  
 }
}