package com.mattrixwv.cipherstream.combination; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mattrixwv.cipherstream.exceptions.InvalidCharacterException; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidKeywordException; import com.mattrixwv.cipherstream.polysubstitution.Columnar; import com.mattrixwv.cipherstream.polysubstitution.PolybiusSquare; /** * Implements the ADFGX cipher, which is a combination of a Polybius square and a columnar transposition cipher. * This class provides methods to encode and decode strings using the ADFGX cipher. * *

* The cipher involves two main steps: *

*
    *
  1. Encoding/decoding with a Polybius square (PolybiusSquare)
  2. *
  3. Encoding/decoding with a columnar transposition cipher (Columnar)
  4. *
*/ public final class ADFGX{ private static final Logger logger = LoggerFactory.getLogger(ADFGX.class); //?Internal fields /** The string that needs encoded/decoded */ protected String inputString; /** The string that is output after encoding/decoding */ protected String outputString; /** The keyword used in the Polybius Square */ protected String squareKeyword; /** The keyword used in the Columnar cipher */ protected String keyword; //?Settings /** Persist capitals in the output string */ protected boolean preserveCapitals; /** Persist whitespace in the output string */ protected boolean preserveWhitespace; /** Persist symbols in the output string */ protected boolean preserveSymbols; //?Internal ciphers /** The first step in encoding */ protected PolybiusSquare polybiusSquare; /** The second step in encoding */ protected Columnar columnar; /** * Sets the Polybius square keyword and validates it. * * @param squareKeyword the keyword for the Polybius square * @throws InvalidKeywordException if the keyword is null */ protected void setSquareKeyword(String squareKeyword) throws InvalidKeywordException{ if(squareKeyword == null){ throw new InvalidKeywordException("Square keyword cannot be null"); } logger.debug("Square keyword '{}'", squareKeyword); this.squareKeyword = squareKeyword; } /** * Sets the columnar cipher keyword and validates it. * * @param keyword the keyword for the columnar cipher * @throws InvalidKeywordException if the keyword is null */ protected void setKeyword(String keyword) throws InvalidKeywordException{ if(keyword == null){ throw new InvalidKeywordException("Keyword cannot be null"); } logger.debug("Keyword '{}'", keyword); this.keyword = keyword; } /** * Sets and sanitizes the input string according to the preservation settings. * * @param inputString the string to be processed * @throws InvalidInputException if the input string is null or blank after processing */ protected void setInputString(String inputString) throws InvalidInputException{ if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Original input string '{}'", inputString); if(!preserveCapitals){ logger.debug("Removing capitals"); inputString = inputString.toUpperCase(); } if(!preserveWhitespace){ logger.debug("Removing whitespace"); inputString = inputString.replaceAll("\\s", ""); } if(!preserveSymbols){ logger.debug("Removing symbols"); inputString = inputString.replaceAll("[^a-zA-Z\\s]", ""); } this.inputString = inputString; logger.debug("Cleaned input string '{}'", inputString); if(this.inputString.isBlank()){ throw new InvalidInputException("Input cannot be blank"); } } /** * Formats the output string to match the original input string when encoding. */ protected void formatOutputStringEncode(){ logger.debug("Formatting output string to match input string"); StringBuilder output = new StringBuilder(); int outputLocation = 0; for(char ch : inputString.toCharArray()){ logger.debug("Input character {}", ch); if(Character.isUpperCase(ch)){ logger.debug("Converting output to uppercase"); output.append(Character.toUpperCase(outputString.charAt(outputLocation++))); output.append(Character.toUpperCase(outputString.charAt(outputLocation++))); } else if(Character.isLowerCase(ch)){ logger.debug("Converting output to lowercase"); output.append(Character.toLowerCase(outputString.charAt(outputLocation++))); output.append(Character.toLowerCase(outputString.charAt(outputLocation++))); } else{ logger.debug("Appending symbol to output"); output.append(ch); } } outputString = output.toString(); logger.debug("Saving output string '{}'", outputString); } /** * Formats the output string to match the original input string when decoding. */ protected void formatOutputStringDecode(){ logger.debug("Formatting output string to match input string"); StringBuilder output = new StringBuilder(); int outputLocation = 0; for(int inputLocation = 0;inputLocation < inputString.length();++inputLocation){ char ch = inputString.charAt(inputLocation); logger.debug("Input character {}", ch); if(Character.isUpperCase(ch)){ logger.debug("Converting output to uppercase"); output.append(Character.toUpperCase(outputString.charAt(outputLocation++))); ++inputLocation; } else if(Character.isLowerCase(ch)){ logger.debug("Converting output to lowercase"); output.append(Character.toLowerCase(outputString.charAt(outputLocation++))); ++inputLocation; } else{ logger.debug("Appending symbol to output"); output.append(ch); } } outputString = output.toString(); logger.debug("Saving output string '{}'", outputString); } /** * Encodes the input string using the Polybius square and columnar cipher. * * @throws InvalidCharacterException if there are invalid characters in the ciphers * @throws InvalidInputException if the input string is invalid * @throws InvalidKeywordException if any of the keywords are invalid */ protected void encode() throws InvalidCharacterException, InvalidInputException, InvalidKeywordException{ //Encode the input with polybius logger.debug("Encoding using Polybius Square"); String polybiusOutput = polybiusSquare.encode(squareKeyword, inputString); squareKeyword = polybiusSquare.getKeyword(); //Change polybius to use the correct symbols logger.debug("Replacing coordinates with letters"); polybiusOutput = polybiusOutput.replace("1", "A").replace("2", "D").replace("3", "F").replace("4", "G").replace("5", "X"); //Encode polybius's output with columnar logger.debug("Encoding using columnar"); String columnarOutput = columnar.encode(keyword, polybiusOutput); keyword = columnar.getKeyword(); outputString = columnarOutput; //Add whatever is needed to the output string formatOutputStringEncode(); } /** * Decodes the input string using the columnar cipher and Polybius square. * * @throws InvalidKeywordException if any of the keywords are invalid * @throws InvalidCharacterException if there are invalid characters in the ciphers * @throws InvalidInputException if the input string is invalid */ protected void decode() throws InvalidKeywordException, InvalidCharacterException, InvalidInputException{ //Decode the input with columnar logger.debug("Decoding using columnar"); String columnarOutput = columnar.decode(keyword, inputString); keyword = columnar.getKeyword(); //Change the symbols to the correct ones for polybius logger.debug("Replacing letters with coordinates"); columnarOutput = columnarOutput.replace("A", "1").replace("D", "2").replace("F", "3").replace("G", "4").replace("X", "5"); //Decode with polybius logger.debug("Decoding using Polybius Square"); String polybiusOutput = polybiusSquare.decode(squareKeyword, columnarOutput); squareKeyword = polybiusSquare.getKeyword(); outputString = polybiusOutput; //Add whatever is needed to the output string formatOutputStringDecode(); } //?Constructor /** * Constructs a new {@code ADFGX} instance with default settings: * capitals, whitespace, and symbols are not preserved. * * @throws InvalidCharacterException if there are invalid characters in the ciphers */ public ADFGX() throws InvalidCharacterException{ preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; reset(); } /** * Constructs a new {@code ADFGX} instance with specified settings for preserving capitals, whitespace, and symbols. * * @param preserveCapitals whether to preserve capital letters in the output * @param preserveWhitespace whether to preserve whitespace in the output * @param preserveSymbols whether to preserve symbols in the output * @throws InvalidCharacterException if there are invalid characters in the ciphers */ public ADFGX(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols) throws InvalidCharacterException{ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; reset(); } /** * Encodes the provided input string using the specified keywords and returns the encoded result. * * @param squareKeyword the keyword for the Polybius square * @param keyword the keyword for the columnar cipher * @param inputString the string to be encoded * @return the encoded string * @throws InvalidKeywordException if any of the keywords are invalid * @throws InvalidInputException if the input string is invalid * @throws InvalidCharacterException if the input contains invalid characters */ public String encode(String squareKeyword, String keyword, String inputString) throws InvalidKeywordException, InvalidInputException, InvalidCharacterException{ setSquareKeyword(squareKeyword); setKeyword(keyword); setInputString(inputString); encode(); return outputString; } /** * Decodes the provided input string using the specified keywords and returns the decoded result. * * @param squareKeyword the keyword for the Polybius square * @param keyword the keyword for the columnar cipher * @param inputString the string to be decoded * @return the decoded string * @throws InvalidKeywordException if any of the keywords are invalid * @throws InvalidInputException if the input string is invalid * @throws InvalidCharacterException if the input contains invalid characters */ public String decode(String squareKeyword, String keyword, String inputString) throws InvalidKeywordException, InvalidInputException, InvalidCharacterException{ setSquareKeyword(squareKeyword); setKeyword(keyword); setInputString(inputString); decode(); return outputString; } //?Getters /** * Returns the current input string. * * @return the input string */ public String getInputString(){ return inputString; } /** * Returns the current output string. * * @return the output string */ public String getOutputString(){ return outputString; } /** * Returns the current Polybius square keyword. * * @return the Polybius square keyword */ public String getSquareKeyword(){ return squareKeyword; } /** * Returns the current columnar cipher keyword. * * @return the columnar cipher keyword */ public String getKeyword(){ return keyword; } /** * Resets all fields to their default values and reinitializes the internal ciphers. * * @throws InvalidCharacterException if there are invalid characters in the ciphers */ public void reset() throws InvalidCharacterException{ logger.debug("Resetting fields"); polybiusSquare = new PolybiusSquare(false, false); columnar = new Columnar(false, false, false, true, 'B'); inputString = ""; outputString = ""; squareKeyword = ""; keyword = ""; } }