Clean up components

This commit is contained in:
Eryn Wells 2015-07-18 22:59:58 -07:00
parent cb437b7d96
commit b7798d2dcd
2 changed files with 62 additions and 68 deletions

View file

@ -9,32 +9,29 @@
import Foundation import Foundation
enum EncoderError: ErrorType {
/** Thrown when an input character is found that is not in the alphabet. */
case InvalidCharacter(ch: Character)
}
protocol Encoder { protocol Encoder {
func encode(c: Character) throws -> Character func encode(c: Character) throws -> Character
} }
class Cryptor { 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. */ /** Array of all possible characters to encrypt. */
static let alphabet: [Character] = Array("ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters) 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 FixedRotor: Cryptor, Encoder {
class Rotor: Cryptor, Encoder {
enum Error: ErrorType { enum Error: ErrorType {
/** Thrown when the initializer is given an invalid series. */ /** Thrown when the initializer is given an invalid series. */
case InvalidSeries 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. */ /** The series of characters that this rotor cycles through. */
let series: [Character]! 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 { func encode(c: Character) throws -> Character {
let offset: Int! = Rotor.alphabet.indexOf(c) if let offset = FixedRotor.alphabet.indexOf(c) {
guard offset != nil else { return series[offset]
throw Error.InvalidCharacter(c: c) } 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. */ /** 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 { class Plugboard: Cryptor, Encoder {
enum Error: ErrorType { private(set) var plugs: [Character: Character] = [:]
case CharacterInMultiplePairs(c: Character)
case PairContainsSameCharacter(c: Character)
}
var pairs: [(Character, Character)]! func addPlug(a: Character, b: Character) {
plugs[a] = b
init(pairs: [(Character, Character)] = []) throws { plugs[b] = a
super.init()
try validatePairs(pairs)
self.pairs = pairs
}
func validatePairs(pairs: [(Character, Character)]) throws {
let alphabetSet = Set(Cryptor.alphabet)
var charactersSeen = Set<Character>()
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 encode(c: Character) throws -> Character { func encode(c: Character) throws -> Character {
for pair in pairs { if let output = plugs[c] {
if c == pair.0 { return output
return pair.1 } else if Plugboard.alphabet.contains(c) {
} return c
if c == pair.1 { } else {
return pair.0 throw EncoderError.InvalidCharacter(ch: c)
}
} }
// If no pair exists, just return the character itself.
return c
} }
} }

View file

@ -15,33 +15,43 @@ class RotorTests: XCTestCase {
let rotorSeries = "EKMFLGDQVZNTOWYHXUSPAIBRCJ" let rotorSeries = "EKMFLGDQVZNTOWYHXUSPAIBRCJ"
func testThatUnadvancedSubstitutionWorks() { func testThatUnadvancedSubstitutionWorks() {
let rotor = try! Enigma.Rotor(series: rotorSeries) let rotor = try! Rotor(series: rotorSeries)
for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rotorSeries.characters) { for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rotorSeries.characters) {
XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter) XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter)
} }
} }
func testThatRotorCanDoRot13() { func testThatRotorCanAdvanceToRot13() {
let rot13Series = "NOPQRSTUVWXYZABCDEFGHIJKLM" let rot13Series = "NOPQRSTUVWXYZABCDEFGHIJKLM"
let rotor = try! Enigma.Rotor(series: alphaSeries) let rotor = try! Rotor(series: alphaSeries)
rotor.advance(13) rotor.advance(13)
for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rot13Series.characters) { for (plainCharacter, cipherCharacter) in zip(alphaSeries.characters, rot13Series.characters) {
XCTAssertEqual(try! rotor.encode(plainCharacter), cipherCharacter) 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 { class PlugboardTests: XCTestCase {
func testThatEmptyPlugboardPassesThroughAllCharacters() { func testThatEmptyPlugboardPassesThroughAllCharacters() {
let plugboard = try! Enigma.Plugboard() let plugboard = Plugboard()
for c in alphaSeries.characters { for c in alphaSeries.characters {
XCTAssertEqual(try! plugboard.encode(c), c) XCTAssertEqual(try! plugboard.encode(c), c)
} }
} }
func testThatPlugboardPairsAreBidirectional() { 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("A"), "H")
XCTAssertEqual(try! plugboard.encode("H"), "A") XCTAssertEqual(try! plugboard.encode("H"), "A")
} }