Clean up components
This commit is contained in:
parent
cb437b7d96
commit
b7798d2dcd
2 changed files with 62 additions and 68 deletions
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue