From 377f8387d2293735bc8e0d418a974a9bad506a10 Mon Sep 17 00:00:00 2001 From: Alexander Lichter Date: Sun, 1 Apr 2018 01:57:24 +0200 Subject: [PATCH] feat: Add Manchester code --- README.md | 1 + docs/ciphers/manchester.md | 102 +++++++++++++++++++++++++++++++++++++ docs/index.md | 1 + src/manchester.js | 78 ++++++++++++++++++++++++++++ test/manchester.test.js | 71 ++++++++++++++++++++++++++ 5 files changed, 253 insertions(+) create mode 100644 docs/ciphers/manchester.md create mode 100644 src/manchester.js create mode 100644 test/manchester.test.js diff --git a/README.md b/README.md index edbef19..d0f9a87 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,7 @@ console.log(rot('Hello world!')) - Fractionated Morse - Pollux - Multi-Tap +- Manchester code ## Contributing diff --git a/docs/ciphers/manchester.md b/docs/ciphers/manchester.md new file mode 100644 index 0000000..dd43e13 --- /dev/null +++ b/docs/ciphers/manchester.md @@ -0,0 +1,102 @@ +# Manchester code + +> Invented by G.E. Thomas in 1949, the Manchester code (also known as PE / Phase Encoding) + is a binary line code mainly used in telecommunication and data storage. The + ciphertext has twice the amount of characters after encoding. + +## Cipher behavior information + +* Case sensitive? ❌ +* Deterministic? ✓ +* Alphabet: `01` +* Characters not in alphabet will be: **omitted** or **throwing an error (default)** + +## Default options object + +The options are the same for both methods, `encode` and `decode` + +``` +const options = { + inverted: false, // Invert the result / treat ciphertext as inverted. + failOnUnknownCharacter: true, // Should an error be thrown when a character is not included in the alphabet +} +``` + +`inverted` can be used to switch between two implementations. G.E. Thomas' (false) and IEEE 802.3 (true) + +## Usage + +### Encoding + +#### Default + +``` +import { manchester } from 'cipher-collection' + + +console.log(manchester.encode('01')) // 0110 +``` + +#### Inverted + + +``` +import { manchester } from 'cipher-collection' + +const invertedOptions = { inverted: true } + +console.log(manchester.encode('01'), invertedOptions) // 1001 +``` + +#### Without error throwing + + +``` +import { manchester } from 'cipher-collection' + +const silentFailOptions = { + failOnUnknownCharacter: false, +} + +console.log(fractionatedMorse.encode('€€€01', silentFailOptions)) // 0110 + +``` + + +### Decoding + + +#### Default + +``` +import { manchester } from 'cipher-collection' + + +console.log(manchester.decode('0110')) // 01 +``` + +#### Inverted + + +``` +import { manchester } from 'cipher-collection' + +const invertedOptions = { inverted: true } + +console.log(manchester.encode('1001'), invertedOptions) // 01 +``` + +#### Without error throwing + + +``` +import { manchester } from 'cipher-collection' + +const silentFailOptions = { + failOnUnknownCharacter: false, +} + +console.log(fractionatedMorse.decode('0', silentFailOptions)) // (empty string) +console.log(fractionatedMorse.decode('€0110', silentFailOptions)) // 01 + +``` diff --git a/docs/index.md b/docs/index.md index 782c71b..94b2e82 100644 --- a/docs/index.md +++ b/docs/index.md @@ -12,4 +12,5 @@ * [Fractionated Morse](./ciphers/fractionated-morse.md) * [Pollux](./ciphers/pollux.md) * [Multi-Tap](./ciphers/multi-tap.md) +* [Manchester code](./ciphers/manchester.md) diff --git a/src/manchester.js b/src/manchester.js new file mode 100644 index 0000000..6e6d523 --- /dev/null +++ b/src/manchester.js @@ -0,0 +1,78 @@ +const decode = (input, options = {}) => { + options = { ...DEFAULT_OPTIONS, ...options } + + if (input.match(/[^01]/g)) { + if (options.failOnUnknownCharacter) { + throw Error('Invalid Input') + } + input = [...input].filter(c => '01'.includes(c)).join('') + } + + if (input.length % 2) { + if (options.failOnUnknownCharacter) { + throw Error('Invalid Input') + } + return '' + } + const splitUp = splitInput(input) + + if (!splitUp || !splitUp.length) { + if (options.failOnUnknownCharacter) { + throw Error('Invalid Input after splitting') + } + return '' + } + + const result = splitUp.map(xorWithClock).join('').split('').filter((_, k) => k % 2 === 0).join('') + + return options.inverted ? invertResult(result) : result +} + +const encode = (input, options = {}) => { + options = { ...DEFAULT_OPTIONS, ...options } + + if (input.match(/[^01]/g)) { + if (options.failOnUnknownCharacter) { + throw Error('Invalid Input') + } + input = [...input].filter(c => '01'.includes(c)).join('') + } + + // Map input to clock + const doubledInput = [...input].map(c => `${c}${c}`).join('') + + const splitUp = splitInput(doubledInput) + + if (!splitUp || !splitUp.length) { + if (options.failOnUnknownCharacter) { + throw Error('Invalid Input after splitting') + } + return '' + } + + const result = splitUp.map(xorWithClock).join('') + + return options.inverted ? invertResult(result) : result +} + +const xorWithClock = i => (Number.parseInt(i, 2) ^ getClock(i.length)).toString(2).padStart(i.length, '0') + +const getClock = len => { + const clock = '01'.repeat(len / 2) + + return Number.parseInt(clock, 2) +} + +const invertResult = r => r.replace(/[01]/g, n => 1 - n) + +const splitInput = i => i.match(/[01]{1,6}/g) + +const DEFAULT_OPTIONS = { + inverted: false, + failOnUnknownCharacter: true +} + +export default { + decode, + encode +} diff --git a/test/manchester.test.js b/test/manchester.test.js new file mode 100644 index 0000000..7f1296e --- /dev/null +++ b/test/manchester.test.js @@ -0,0 +1,71 @@ +import manchester from 'manchester' + +const silentFailOptions = { + failOnUnknownCharacter: false +} + +const invertedOptions = { + inverted: true +} + +describe('encoding', () => { + test('default', () => { + expect(manchester.encode('0')).toBe('01') + expect(manchester.encode('01')).toBe('0110') + expect(manchester.encode('010')).toBe('011001') + expect(manchester.encode('0010110101000101')).toBe('01011001101001100110010101100110') + }) + + test('inverted', () => { + expect(manchester.encode('0', invertedOptions)).toBe('10') + expect(manchester.encode('01', invertedOptions)).toBe('1001') + expect(manchester.encode('010', invertedOptions)).toBe('100110') + expect(manchester.encode('0010110101000101', invertedOptions)).toBe('10100110010110011001101010011001') + }) + + test('empty', () => { + expect(manchester.encode('', silentFailOptions)).toBe('') + expect(() => { manchester.encode('') }).toThrowError('Invalid Input after splitting') + }) + + test('invalid', () => { + expect(manchester.encode('A', silentFailOptions)).toBe('') + expect(manchester.encode('€', silentFailOptions)).toBe('') + expect(manchester.encode('€0', silentFailOptions)).toBe('01') + + expect(() => { manchester.encode('A') }).toThrowError('Invalid Input') + expect(() => { manchester.encode('€') }).toThrowError('Invalid Input') + }) +}) + +describe('decoding', () => { + test('default', () => { + expect(manchester.decode('01')).toBe('0') + expect(manchester.decode('0110')).toBe('01') + expect(manchester.decode('011001')).toBe('010') + expect(manchester.decode('01011001101001100110010101100110')).toBe('0010110101000101') + }) + + test('inverted', () => { + expect(manchester.decode('10', invertedOptions)).toBe('0') + expect(manchester.decode('1001', invertedOptions)).toBe('01') + expect(manchester.decode('100110', invertedOptions)).toBe('010') + expect(manchester.decode('10100110010110011001101010011001', invertedOptions)).toBe('0010110101000101') + }) + + test('empty', () => { + expect(manchester.decode('', silentFailOptions)).toBe('') + expect(() => { manchester.decode('') }).toThrowError('Invalid Input after splitting') + }) + + test('invalid', () => { + expect(manchester.decode('0', silentFailOptions)).toBe('') + expect(manchester.decode('A', silentFailOptions)).toBe('') + expect(manchester.decode('€', silentFailOptions)).toBe('') + expect(manchester.decode('€01', silentFailOptions)).toBe('0') + + expect(() => { manchester.decode('0') }).toThrowError('Invalid Input') + expect(() => { manchester.decode('A') }).toThrowError('Invalid Input') + expect(() => { manchester.decode('€') }).toThrowError('Invalid Input') + }) +})