//CipherStreamJava/src/main/java/com/mattrixwv/CipherStreamJava/polySubstitution/RailFence.java //Mattrixwv // Created: 03-21-22 //Modified: 07-09-22 package com.mattrixwv.cipherstream.polysubstitution; import java.math.BigDecimal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mattrixwv.cipherstream.exceptions.InvalidInputException; import com.mattrixwv.cipherstream.exceptions.InvalidBaseException; public class RailFence{ private static final Logger logger = LoggerFactory.getLogger(RailFence.class); //Fields private String inputString; //The message that needs to be encoded/decoded private String outputString; //The encoded/decoded message private StringBuilder[] fence; //The fence used for encoding/decoding private boolean preserveCapitals; //Persist capitals in the output string private boolean preserveWhitespace; //Persist whitespace in the output string private boolean preserveSymbols; //Persist symbols in the output string //Strips invalid characters from the string that needs encoded/decoded private void setInputString(String inputString) throws InvalidInputException{ //Ensure the input string isn't null if(inputString == null){ throw new InvalidInputException("Input cannot be null"); } logger.debug("Original input string '{}'", inputString); //Apply removal options if(!preserveCapitals){ logger.debug("Removing case"); 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]", ""); } //Save the string logger.debug("Clean input string '{}'", inputString); this.inputString = inputString; //Ensure the string isn't blank if(this.inputString.isBlank()){ throw new InvalidInputException("Input must contain at least 1 letter"); } } //Ensures the number of rails is valid and sets up the fence private void setNumRails(int numRails) throws InvalidBaseException{ if(numRails < 2){ throw new InvalidBaseException("You must use at least 2 rails"); } logger.debug("Creating {} rails", numRails); fence = new StringBuilder[numRails]; for(int cnt = 0;cnt < numRails;++cnt){ fence[cnt] = new StringBuilder(); } } //Strip the inputString of all non-letter characters private String getCleanInputString(){ logger.debug("Getting input string for encoding"); return inputString.replaceAll("[^a-zA-Z]", ""); } //Ensures capitals, lowercase, and symbols are displayed in the output string private void formatOutput(String outputString){ logger.debug("Formatting output string"); StringBuilder output = new StringBuilder(); int outputLoc = 0; for(char ch : inputString.toCharArray()){ logger.debug("Working character {}", ch); if(Character.isUpperCase(ch)){ logger.debug("Formatting uppercase"); output.append(Character.toUpperCase(outputString.charAt(outputLoc++))); } else if(Character.isLowerCase(ch)){ logger.debug("Formatting lowercase"); output.append(Character.toLowerCase(outputString.charAt(outputLoc++))); } else{ logger.debug("Inserting symbol"); output.append(ch); } } logger.debug("Formatted output '{}'", output); this.outputString = output.toString(); } //Returns the decoded string found in the fence after all characters are placed correctly private String getDecodedStringFromFence(){ logger.debug("Getting decoded string from the fence"); boolean down = true; int rail = 0; int outsideCol = 0; int insideCol = -1; StringBuilder output = new StringBuilder(); while(true){ //Get the next character based on what rail you are currently usinig if(rail == 0){ if(outsideCol >= fence[rail].length()){ break; } output.append(fence[rail].charAt(outsideCol)); ++insideCol; } else if(rail == (fence.length - 1)){ if(outsideCol >= fence[rail].length()){ break; } output.append(fence[rail].charAt(outsideCol++)); ++insideCol; } else{ if(insideCol >= fence[rail].length()){ break; } output.append(fence[rail].charAt(insideCol)); } //Make sure you're still in bounds if(down){ ++rail; } else{ --rail; } if(rail >= fence.length){ down = false; rail -= 2; } else if(rail < 0){ down = true; rail += 2; } } logger.debug("Fence output '{}'", output); return output.toString(); } //Encodes inputString using the RailFence cipher and stores the result in outputString private void encode(){ logger.debug("Encoding"); boolean up = true; int rail = 0; for(char ch : getCleanInputString().toCharArray()){ logger.debug("Working character {}", ch); fence[rail].append(ch); //Advance to the next rail if(up){ logger.debug("Moving up"); ++rail; } else{ logger.debug("Moving down"); --rail; } //Make sure you're still in bounds if(rail == fence.length){ logger.debug("Swapping to down"); up = false; rail -= 2; } else if(rail == -1){ logger.debug("Swapping to up"); up = true; rail += 2; } } //Append the fence rows to come up with a single string logger.debug("Appending rows from the fence"); StringBuilder output = new StringBuilder(); for(StringBuilder segment : fence){ output.append(segment); } //Format the output formatOutput(output.toString()); } //Decodes inputString using the RailFence cipher and stores the result in outputString private void decode(){ logger.debug("Decoding"); //Determine the number of characters on each rail String cleanInputString = getCleanInputString(); int cycleLength = 2 * (fence.length - 1); BigDecimal k = new BigDecimal(cleanInputString.length()).divide(new BigDecimal(cycleLength)); int numInTopRail = (int)Math.ceil(k.doubleValue()); int numInMiddleRails = (numInTopRail * 2); int numInBottomRail = 0; boolean goingDown = true; int middleNum = 0; if(k.remainder(BigDecimal.ONE).compareTo(new BigDecimal("0.5")) <= 0){ numInMiddleRails -= 1; numInBottomRail = (int)Math.floor(k.doubleValue()); goingDown = true; middleNum = k.remainder(BigDecimal.ONE).multiply(new BigDecimal(cycleLength)).intValue() - 1; } else{ numInBottomRail = numInTopRail; goingDown = false; middleNum = (cycleLength - (k.remainder(BigDecimal.ONE).multiply(new BigDecimal(cycleLength))).intValue()); } logger.debug("Number of characters in the top rail {}", numInTopRail); logger.debug("Number of characters in the middle rails {}", numInMiddleRails); logger.debug("Number of characters in the bottom rail {}", numInBottomRail); //Add the correct number of characters to each rail logger.debug("Adding characters to the rails"); fence[0].append(cleanInputString.substring(0, numInTopRail)); int start = numInTopRail; int end = numInTopRail + numInMiddleRails; for(int cnt = 1;cnt < (fence.length - 1);++cnt){ if((!goingDown) && (middleNum >= cnt)){ end -= 1; } fence[cnt].append(cleanInputString.substring(start, end)); start = end; end += numInMiddleRails; } end = start + numInBottomRail; logger.debug("Appending the bottom rail"); fence[fence.length - 1].append(cleanInputString.substring(start, end)); //Get the decoded string from the constructed fence String output = getDecodedStringFromFence(); logger.debug("Fence output '{}'", output); formatOutput(output); } //Constructor public RailFence(){ preserveCapitals = false; preserveWhitespace = false; preserveSymbols = false; } public RailFence(boolean preserveCapitals, boolean preserveWhitespace, boolean preserveSymbols){ this.preserveCapitals = preserveCapitals; this.preserveWhitespace = preserveWhitespace; this.preserveSymbols = preserveSymbols; } //Encodes inputString using a Rail Fence of length numRails and returns the result public String encode(int numRails, String inputString) throws InvalidBaseException, InvalidInputException{ //Set the parameters setNumRails(numRails); setInputString(inputString); //Encode encode(); return outputString; } //Decodes inputString using a Rail Fence of length numRails and returns the result public String decode(int numRails, String inputString) throws InvalidBaseException, InvalidInputException{ //Set the parameters setNumRails(numRails); setInputString(inputString); //Decode decode(); return outputString; } //Makes sure all variables are empty public void reset(){ logger.debug("Resetting fields"); inputString = ""; outputString = ""; fence = null; } //Gets public String getInputString(){ return inputString; } public String getOutputString(){ return outputString; } public int getNumRails(){ return fence.length; } }