From b7798d2dcdc815e488179421071b403fa0375b19 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Sat, 18 Jul 2015 22:59:58 -0700 Subject: [PATCH] Clean up components --- Enigma/Rotor.swift | 110 +++++++++++++++------------------- EnigmaTests/EnigmaTests.swift | 20 +++++-- 2 files changed, 62 insertions(+), 68 deletions(-) diff --git a/Enigma/Rotor.swift b/Enigma/Rotor.swift index 1f0d83f..d776f85 100644 --- a/Enigma/Rotor.swift +++ b/Enigma/Rotor.swift @@ -9,32 +9,29 @@ import Foundation +enum EncoderError: ErrorType { + /** Thrown when an input character is found that is not in the alphabet. */ + case InvalidCharacter(ch: Character) +} + + protocol Encoder { func encode(c: Character) throws -> Character } class Cryptor { - enum Error: ErrorType { - /** Thrown when encode() encounters a character that is not in the alphabet. */ - case InvalidCharacter(c: Character) - } - /** Array of all possible characters to encrypt. */ static let alphabet: [Character] = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters) } -/** Rotors are Cryptors that have a position, which offsets the alphabet from the series and changes which character is substituted for a given input. */ -class Rotor: Cryptor, Encoder { +class FixedRotor: Cryptor, Encoder { enum Error: ErrorType { /** Thrown when the initializer is given an invalid series. */ case InvalidSeries } - /** The position of first letter in `series` in the `alphabet`. */ - var position: Int = 0 - /** The series of characters that this rotor cycles through. */ let series: [Character]! @@ -50,71 +47,58 @@ class Rotor: Cryptor, Encoder { } } - func advance(count: Int = 1) { - position = (position + count) % Rotor.alphabet.count - } - func encode(c: Character) throws -> Character { - let offset: Int! = Rotor.alphabet.indexOf(c) - guard offset != nil else { - throw Error.InvalidCharacter(c: c) + if let offset = FixedRotor.alphabet.indexOf(c) { + return series[offset] + } else { + throw EncoderError.InvalidCharacter(ch: c) } - return series[(offset + position) % series.count] } } +/** Rotors are FixedRotors that have a variable position, which offsets the alphabet from the series and changes which character is substituted for a given input. */ +class Rotor: FixedRotor { + /** The position of first letter in `series` in the `alphabet`. */ + var position: Int = 0 { + willSet { + self.position = newValue % Cryptor.alphabet.count + } + } + + func advance(count: Int = 1) { + position = (position + count) % Rotor.alphabet.count + } + + override func encode(c: Character) throws -> Character { + if let offset = Rotor.alphabet.indexOf(c) { + return series[(offset + position) % series.count] + } else { + throw EncoderError.InvalidCharacter(ch: c) + } + } +} + + +class Reflector: FixedRotor { } + + /** A Plugboard is a Cryptor that substitutes one character for another based on a set of pairs. A pair of characters is mutually exclusive of other pairs; that is, a character can only belong to one pair. Furthermore, the Plugboard always trades one character for the same character and vice versa. */ class Plugboard: Cryptor, Encoder { - enum Error: ErrorType { - case CharacterInMultiplePairs(c: Character) - case PairContainsSameCharacter(c: Character) - } + private(set) var plugs: [Character: Character] = [:] - var pairs: [(Character, Character)]! - - init(pairs: [(Character, Character)] = []) throws { - super.init() - try validatePairs(pairs) - self.pairs = pairs - } - - func validatePairs(pairs: [(Character, Character)]) throws { - let alphabetSet = Set(Cryptor.alphabet) - var charactersSeen = Set() - for pair in pairs { - if !alphabetSet.contains(pair.0) { - throw Cryptor.Error.InvalidCharacter(c: pair.0) - } - if !alphabetSet.contains(pair.1) { - throw Cryptor.Error.InvalidCharacter(c: pair.1) - } - if pair.0 == pair.1 { - throw Error.PairContainsSameCharacter(c: pair.0) - } - if charactersSeen.contains(pair.0) { - throw Error.CharacterInMultiplePairs(c: pair.0) - } else { - charactersSeen.insert(pair.0) - } - if charactersSeen.contains(pair.1) { - throw Error.CharacterInMultiplePairs(c: pair.1) - } else { - charactersSeen.insert(pair.1) - } - } + func addPlug(a: Character, b: Character) { + plugs[a] = b + plugs[b] = a } func encode(c: Character) throws -> Character { - for pair in pairs { - if c == pair.0 { - return pair.1 - } - if c == pair.1 { - return pair.0 - } + if let output = plugs[c] { + return output + } else if Plugboard.alphabet.contains(c) { + return c + } else { + throw EncoderError.InvalidCharacter(ch: c) } - // If no pair exists, just return the character itself. - return c } } \ No newline at end of file diff --git a/EnigmaTests/EnigmaTests.swift b/EnigmaTests/EnigmaTests.swift index faf5c27..359d0d0 100644 --- a/EnigmaTests/EnigmaTests.swift +++ b/EnigmaTests/EnigmaTests.swift @@ -15,33 +15,43 @@ class RotorTests: XCTestCase { let rotorSeries = "EKMFLGDQVZNTOWYHXUSPAIBRCJ" func testThatUnadvancedSubstitutionWorks() { - let rotor = try! Enigma.Rotor(series: rotorSeries) + let rotor = try! Rotor(series: rotorSeries) for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rotorSeries.characters) { XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter) } } - func testThatRotorCanDoRot13() { + func testThatRotorCanAdvanceToRot13() { let rot13Series = "NOPQRSTUVWXYZABCDEFGHIJKLM" - let rotor = try! Enigma.Rotor(series: alphaSeries) + let rotor = try! Rotor(series: alphaSeries) rotor.advance(13) for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rot13Series.characters) { XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter) } } + + func testThatRotorCanSetToRot13() { + let rot13Series = "NOPQRSTUVWXYZABCDEFGHIJKLM" + let rotor = try! Rotor(series: alphaSeries) + rotor.position = 13 + for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rot13Series.characters) { + XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter) + } + } } class PlugboardTests: XCTestCase { func testThatEmptyPlugboardPassesThroughAllCharacters() { - let plugboard = try! Enigma.Plugboard() + let plugboard = Plugboard() for c in alphaSeries.characters { XCTAssertEqual(try! plugboard.encode(c), c) } } func testThatPlugboardPairsAreBidirectional() { - let plugboard = try! Enigma.Plugboard(pairs: [("A", "H")]) + let plugboard = Plugboard() + plugboard.addPlug("A", b: "H") XCTAssertEqual(try! plugboard.encode("A"), "H") XCTAssertEqual(try! plugboard.encode("H"), "A") }