Skip to content

Commit

Permalink
feat: Add Fractionated Morse cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
manniL committed Mar 31, 2018
1 parent 257f90f commit 6cd7ec1
Show file tree
Hide file tree
Showing 6 changed files with 262 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ console.log(rot('Hello world!'))

- ROT-N (custom number of rotations, optional number rotation)
- Morse (custom delimiter, custom handling of unknown characters)
- Fractionated Morse


## Contributing
Expand Down
94 changes: 94 additions & 0 deletions docs/ciphers/fractionated-morse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Fractionated Morse

> A doubling substitution cipher based on the Morse keyAlphabet.
## Cipher behavior information

* Case sensitive? ❌
* Alphabet: `ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890 .,:;?-_()'=+/@`
* Characters not in keyAlphabet will be: **omitted** or **throwing an error (default)**

## Default options object

The options are the same for both methods, `encode` and `decode`

```
const options = {
keyAlphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', // Order of letters to encode/decode the input. Must contain 26 unique characters
failOnUnknownCharacter: true, // Should an error be thrown when a character is not included in the alphabet
}
```

## Usage

### Encoding

#### Default

```
import { fractionatedMorse } from 'cipher-collection'
console.log(fractionatedMorse.encode('Hello World')) // AGTCDHOTQODTCJ
```

#### With custom alphabet

```
import { fractionatedMorse } from 'cipher-collection'
const options = { alphabet: 'BCADEFGHIJKLMNOPQRSTUVWXYZ' }
console.log(fractionatedMorse.encode('Hello World', options)) // BGTADHOTQODTAJ
```

#### Without error throwing


```
import { fractionatedMorse } from 'cipher-collection'
const silentFailOptions = {
failOnUnknownCharacter: false,
}
// Characters will be omitted
console.log(fractionatedMorse.encode('€€€Hello World', silentFailOptions)) //AGTCDHOTQODTCJ
```


### Decoding

#### Default

```
import { fractionatedMorse } from 'cipher-collection'
console.log(fractionatedMorse.decode('AGTCDHOTQODTCJ')) // Hello World
```

#### With custom alphabet

```
import { fractionatedMorse } from 'cipher-collection'
const options = { alphabet: 'BCADEFGHIJKLMNOPQRSTUVWXYZ' }
console.log(fractionatedMorse.decode('BGTADHOTQODTAJ', options)) // Hello World
```

#### Without error throwing


```
import { fractionatedMorse } from 'cipher-collection'
const silentFailOptions = {
failOnUnknownCharacter: false,
}
console.log(fractionatedMorse.decode('€€€AGTCDHOTQODTCJ', silentFailOptions)) //Hello World
```
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@

* [ROT-N](./ciphers/rot.md)
* [Morse](./ciphers/morse.md)
* [Fractionated Morse](./ciphers/fractionated-morse.md)

106 changes: 106 additions & 0 deletions src/fractionatedMorse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import morse from './morse'

export const decode = (input, options = {}) => {
options = { ...DEFAULT_OPTIONS, ...options }
const morseOptions = { ...DEFAULT_MORSE_OPTION, ...{ failOnUnknownCharacter: options.failOnUnknownCharacter } }

let morseCode = [...input].map(c => {
const decodedCharacterIndex = options.keyAlphabet.indexOf(c)
if (decodedCharacterIndex !== -1) {
return ENCODED_ALPHABET[decodedCharacterIndex]
}
if (options.failOnUnknownCharacter) {
throw Error(`Undecodable character ${c}`)
}
return ''
})
.join('')

// Remove padding if needed
.replace(/x{1,2}$/, '')
// Fix unwanted space encoding
.replace(/xx/g, SPACE_STRING)

return morse.decode(morseCode, morseOptions)
}

export const encode = (input, options = {}) => {
options = { ...DEFAULT_OPTIONS, ...options }
const morseOptions = { ...DEFAULT_MORSE_OPTION, ...{ failOnUnknownCharacter: options.failOnUnknownCharacter } }

return morseCodeFromInput(input, morseOptions)
// Split into arrays containing three-character strings
.match(/.{3}/g)
.map(c => {
const encodedCharacterIndex = ENCODED_ALPHABET.indexOf(c)

if (encodedCharacterIndex !== -1) {
return options.keyAlphabet[encodedCharacterIndex]
}

if (options.failOnUnknownCharacter) {
throw Error(`Unencodable character ${c}`)
}

return ''
}).join('')
}

const morseCodeFromInput = (input, morseOptions) => {
let morseCode = morse.encode(input.toUpperCase(), morseOptions)

// Add padding if needed
if (morseCode.length % 3) {
morseCode += morseOptions.separator.repeat(3 - (morseCode.length % 3))
}

return morseCode
.replace(new RegExp(`${morseOptions.separator}`, 'g'), 'x')
// Fix unwanted space encoding
.replace(new RegExp(`${SPACE_STRING}`, 'g'), 'xx')
}

const ENCODED_ALPHABET = [
'...',
'..-',
'..x',
'.-.',
'.--',
'.-x',
'.x.',
'.x-',
'.xx',
'-..',
'-.-',
'-.x',
'--.',
'---',
'--x',
'-x.',
'-x-',
'-xx',
'x..',
'x.-',
'x.x',
'x-.',
'x--',
'x-x',
'xx.',
'xx-'
]

const SPACE_STRING = 'x/x'

const DEFAULT_OPTIONS = {
keyAlphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
failOnUnknownCharacter: true
}

const DEFAULT_MORSE_OPTION = {
separator: 'x'
}

export default {
decode,
encode
}
4 changes: 3 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import rot from './rot'
import morse from './morse'
import fractionatedMorse from './fractionatedMorse'

export default {
rot,
morse
morse,
fractionatedMorse
}
57 changes: 57 additions & 0 deletions test/fractionatedMorse.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fractionatedMorse from 'fractionatedMorse'

const alphabetOptions = {
keyAlphabet: 'BCADEFGHIJKLMNOPQRSTUVWXYZ'
}

const silentFailOptions = {
failOnUnknownCharacter: false
}

describe('encoding', () => {
test('default', () => {
expect(fractionatedMorse.encode('ZZZZ')).toBe('MHJWCMI')
expect(fractionatedMorse.encode('Hello World')).toBe('AGTCDHOTQODTCJ')
expect(fractionatedMorse.encode('OKAY LETS TRY SOMETHING WEIRD')).toBe('NVPQEYJUPCXDVOSHOOHSCCLMYOGGLJ')
expect(fractionatedMorse.encode('A')).toBe('F')
expect(fractionatedMorse.encode('D')).toBe('J')
expect(fractionatedMorse.encode('E')).toBe('I')
expect(fractionatedMorse.encode('1234567890.,:;?-_()\'=+/@')).toBe('EOBOAOAFACJCMCNCNLNODKWBQMCKDSMHAFBKVMVMPNLJFDLJLED')
expect(fractionatedMorse.encode('A D E F')).toBe('FVIIBI')

expect(() => { fractionatedMorse.encode('€€€') }).toThrowError('Unencodable character')
expect(() => { fractionatedMorse.encode('Ü') }).toThrowError('Unencodable character')
expect(() => { fractionatedMorse.encode('A A') }).toThrowError('Unencodable character')
})

test('with different keyAlphabet', () => {
expect(fractionatedMorse.encode('AI', alphabetOptions)).toBe('FA')
expect(fractionatedMorse.encode('HELLO WORLD', alphabetOptions)).toBe('BGTADHOTQODTAJ')
})
test('with silent fail', () => {
expect(fractionatedMorse.encode('€€€A', silentFailOptions)).toBe('F')
expect(fractionatedMorse.encode('€€€Hello World', silentFailOptions)).toBe('AGTCDHOTQODTCJ')
})
})

describe('decoding', () => {
test('default', () => {
expect(fractionatedMorse.decode('F')).toBe('A')
expect(fractionatedMorse.decode('J')).toBe('D')
expect(fractionatedMorse.decode('I')).toBe('E')
expect(fractionatedMorse.decode('AGTCDHOTQODTCJ')).toBe('HELLO WORLD')
expect(fractionatedMorse.decode('EOBOAOAFACJCMCNCNLNODKWBQMCKDSMHAFBKVMVMPNLJFDLJLED')).toBe('1234567890.,:;?-_()\'=+/@')
expect(fractionatedMorse.decode('FT')).toBe('A A')
expect(fractionatedMorse.decode('FVIIBI')).toBe('A D E F')
expect(() => { fractionatedMorse.decode('ÜÄÖ') }).toThrowError('Undecodable character')
})

test('with different keyAlphabet', () => {
expect(fractionatedMorse.decode('FA', alphabetOptions)).toBe('AI')
expect(fractionatedMorse.decode('BGTADHOTQODTAJ', alphabetOptions)).toBe('HELLO WORLD')
})

test('with silent fail', () => {
expect(fractionatedMorse.decode('€€€F', silentFailOptions)).toBe('A')
})
})

0 comments on commit 6cd7ec1

Please sign in to comment.