Skip to content

Commit 741cbaa

Browse files
committed
Add char sets
Base16, Crockford32 and WordSafe32
1 parent 7281178 commit 741cbaa

File tree

8 files changed

+155
-23
lines changed

8 files changed

+155
-23
lines changed

README.md

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -209,29 +209,48 @@ Example:
209209

210210
### <a name="Chars"></a>Chars
211211

212-
There are 16 pre-defined character sets:
213-
214-
| Name | Characters |
215-
| :------------- | :-------------------------------------------------------------------------------------------- |
216-
| Alpha | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
217-
| AlphaLower | abcdefghijklmnopqrstuvwxyz |
218-
| AlphaUpper | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
219-
| Alphanum | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |
220-
| AlphanumLower | abcdefghijklmnopqrstuvwxyz0123456789 |
221-
| AlphanumUpper | ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 |
222-
| Base32 | 234567ABCDEFGHIJKLMNOPQRSTUVWXYZ |
223-
| Base32Hex | 0123456789abcdefghijklmnopqrstuv |
224-
| base32HexUpper | 0123456789ABCDEFGHIJKLMNOPQRSTUV |
225-
| Decimal | 0123456789 |
226-
| Hex | 0123456789abcdef |
227-
| HexUpper | 0123456789ABCDEF |
228-
| SafeAscii | !#$%&()\*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^\_abcdefghijklmnopqrstuvwxyz{\|}~ |
229-
| Safe32 | 2346789bdfghjmnpqrtBDFGHJLMNPQRT |
230-
| Safe64 | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-\_ |
231-
| Symbol | !#$%&()\*+,-./:;<=>?@[]^\_{\|}~ |
212+
There are 19 pre-defined character sets:
213+
214+
| Name | Characters |
215+
| :---------------- | :-------------------------------------------------------------------------------------------- |
216+
| :alpha | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz |
217+
| :alpha_lower | abcdefghijklmnopqrstuvwxyz |
218+
| :alpha_upper | ABCDEFGHIJKLMNOPQRSTUVWXYZ |
219+
| :alphanum | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 |
220+
| :alphanum_lower | abcdefghijklmnopqrstuvwxyz0123456789 |
221+
| :alphanum_upper | ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 |
222+
| :base16 | 0123456789ABCDEF |
223+
| :base32 | ABCDEFGHIJKLMNOPQRSTUVWXYZ234567 |
224+
| :base32_hex | 0123456789abcdefghijklmnopqrstuv |
225+
| :base32_hex_upper | 0123456789ABCDEFGHIJKLMNOPQRSTUV |
226+
| :crockford32 | 0123456789ABCDEFGHJKMNPQRSTVWXYZ |
227+
| :decimal | 0123456789 |
228+
| :hex | 0123456789abcdef |
229+
| :hex_upper | 0123456789ABCDEF |
230+
| :safe_ascii | !#$%&()\*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^\_abcdefghijklmnopqrstuvwxyz{\|}~ |
231+
| :safe32 | 2346789bdfghjmnpqrtBDFGHJLMNPQRT |
232+
| :safe64 | ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-\_ |
233+
| :symbol | !#$%&()\*+,-./:;<=>?@[]^\_{\|}~ |
234+
| :wordSafe32 | 23456789CFGHJMPQRVWXcfghjmpqrvwx |
232235

233236
Any string of up to 256 unique characters, including unicode, can be used for **`puid`** generation.
234237

238+
#### Description of non-obvious character sets
239+
240+
| Name | Description |
241+
| :---------------- | :--------------------------------------------------------- |
242+
| :base16 | https://datatracker.ietf.org/doc/html/rfc4648#section-8 |
243+
| :base32 | https://datatracker.ietf.org/doc/html/rfc4648#section-6 |
244+
| :base32_hex | Lowercase of :base32_hex_upper |
245+
| :base32_hex_upper | https://datatracker.ietf.org/doc/html/rfc4648#section-7 |
246+
| :crockford32 | https://www.crockford.com/base32.html |
247+
| :safe_ascii | Printable ascii that does not require escape in String |
248+
| :safe32 | Alpha and numbers picked to reduce chance of English words |
249+
| :safe64 | https://datatracker.ietf.org/doc/html/rfc4648#section-5 |
250+
| :wordSafe32 | Alpha and numbers picked to reduce chance of English words |
251+
252+
Note: :safe32 and :wordSafe32 are two different strategies for the same goal.
253+
235254
[TOC](#TOC)
236255

237256
## <a name="Motivation"></a>Motivation

src/lib/chars.spec.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,18 @@ test('pre-defined Chars', (t) => {
1010
Chars.AlphaNumLower,
1111
Chars.AlphaNumUpper,
1212
Chars.AlphaUpper,
13+
Chars.Base16,
1314
Chars.Base32,
1415
Chars.Base32Hex,
1516
Chars.Base32HexUpper,
17+
Chars.Crockford32,
1618
Chars.Decimal,
1719
Chars.Hex,
1820
Chars.HexUpper,
1921
Chars.SafeAscii,
2022
Chars.Safe32,
21-
Chars.Safe64
23+
Chars.Safe64,
24+
Chars.WordSafe32
2225
]
2326

2427
allChars.forEach((chars) => t.is(validChars(chars)[0], true))

src/lib/chars.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@ export enum Chars {
1010
AlphaNum = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
1111
AlphaNumLower = 'abcdefghijklmnopqrstuvwxyz0123456789',
1212
AlphaNumUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
13+
Base16 = '0123456789ABCDEF',
1314
Base32 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567',
1415
Base32Hex = '0123456789abcdefghijklmnopqrstuv',
1516
Base32HexUpper = '0123456789ABCDEFGHIJKLMNOPQRSTUV',
17+
Crockford32 = '0123456789ABCDEFGHJKMNPQRSTVWXYZ',
1618
Decimal = '0123456789',
1719
Hex = '0123456789abcdef',
1820
HexUpper = '0123456789ABCDEF',
1921
SafeAscii = '!#$%&()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~',
2022
Safe32 = '2346789bdfghjmnpqrtBDFGHJLMNPQRT',
2123
Safe64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
22-
Symbol = '!#$%&()*+,-./:;<=>?@[]^_{|}~'
24+
Symbol = '!#$%&()*+,-./:;<=>?@[]^_{|}~',
25+
WordSafe32 = '23456789CFGHJMPQRVWXcfghjmpqrvwx'
2326
}
2427

2528
export const charsName = (chars: string): string => {
@@ -29,15 +32,18 @@ export const charsName = (chars: string): string => {
2932
if (chars === Chars.AlphaNum) return 'alphaNum'
3033
if (chars === Chars.AlphaNumLower) return 'alphaNumLower'
3134
if (chars === Chars.AlphaNumUpper) return 'alphaNumUpper'
35+
if (chars === Chars.Base16) return 'base16'
3236
if (chars === Chars.Base32) return 'base32'
3337
if (chars === Chars.Base32Hex) return 'base32Hex'
3438
if (chars === Chars.Base32HexUpper) return 'base32HexUpper'
39+
if (chars === Chars.Crockford32) return 'crockford32'
3540
if (chars === Chars.Hex) return 'hex'
3641
if (chars === Chars.HexUpper) return 'hexUpper'
3742
if (chars === Chars.Safe32) return 'safe32'
3843
if (chars === Chars.Safe64) return 'safe64'
3944
if (chars === Chars.SafeAscii) return 'safeAscii'
4045
if (chars === Chars.Symbol) return 'symbol'
46+
if (chars === Chars.WordSafe32) return 'wordSafe32'
4147
return 'custom'
4248
}
4349

src/lib/encoder.spec.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@ test('alphaCase(true) encoder chars', (t) => chars_encoder(t, Chars.AlphaUpper))
1616
test('alphaNumEncoder() encoder chars', (t) => chars_encoder(t, Chars.AlphaNum))
1717
test('alphaNumCaseEncoder() encoder chars', (t) => chars_encoder(t, Chars.AlphaNumLower))
1818
test('alphaNumCaseEncoder(true) encoder chars', (t) => chars_encoder(t, Chars.AlphaNumUpper))
19+
test('base16Encoder() encoder chars', (t) => chars_encoder(t, Chars.Base16))
1920
test('base32Encoder() encoder chars', (t) => chars_encoder(t, Chars.Base32))
2021
test('base32HexCaseEncoder() encoder chars', (t) => chars_encoder(t, Chars.Base32Hex))
2122
test('base32HexCaseEncoder(true) encoder chars', (t) => chars_encoder(t, Chars.Base32HexUpper))
23+
test('crockford32() encoder chars', (t) => chars_encoder(t, Chars.Crockford32))
2224
test('decimalEncoder() encoder chars', (t) => chars_encoder(t, Chars.Decimal))
2325
test('hexCaseEncoder() encoder chars', (t) => chars_encoder(t, Chars.Hex))
2426
test('hexCaseEncoder(true) encoder chars', (t) => chars_encoder(t, Chars.HexUpper))
2527
test('safeAsciiEncoder() encoder chars', (t) => chars_encoder(t, Chars.SafeAscii))
2628
test('safe32Encoder() encoder chars', (t) => chars_encoder(t, Chars.Safe32))
2729
test('safe64Encoder encoder chars', (t) => chars_encoder(t, Chars.Safe64))
2830
test('symbolEncoder() encoder chars', (t) => chars_encoder(t, Chars.Symbol))
31+
test('wordSafe32Encoder() encoder chars', (t) => chars_encoder(t, Chars.WordSafe32))

src/lib/encoder/base16.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { PuidEncoder } from '../../types/puid'
2+
3+
export default (): PuidEncoder => {
4+
const decimal = '0'.charCodeAt(0)
5+
const alpha = 'A'.charCodeAt(0) - 10
6+
7+
return (n: number) => n + (n < 10 ? decimal : alpha)
8+
}

src/lib/encoder/crockford32.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { PuidEncoder } from '../../types/puid'
2+
3+
// n: 0123456789 01234567 89 01 23456 78901
4+
// c: 0123456789 ABCDEFGH JK MN PQRST VWXYZ
5+
6+
export default (): PuidEncoder => {
7+
const A = 'A'.charCodeAt(0)
8+
const J = 'J'.charCodeAt(0)
9+
const M = 'M'.charCodeAt(0)
10+
const P = 'P'.charCodeAt(0)
11+
const V = 'V'.charCodeAt(0)
12+
13+
return (n: number) => {
14+
if (n < 10) return n
15+
if (n < 18) return n - 10 + A
16+
if (n < 20) return n - 18 + J
17+
if (n < 22) return n - 20 + M
18+
if (n < 27) return n - 22 + P
19+
if (n < 31) return n - 27 + V
20+
return NaN
21+
}
22+
}

src/lib/encoder/wordSafe32.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { PuidEncoder } from '../../types/puid'
2+
3+
// n: 01234567 8 901 2 3 456 789 0 123 4 5 678 901
4+
// c: 23456789 C FGH J M PQR VWX c fgh j m pqr vwx
5+
6+
export default (): PuidEncoder => {
7+
const two = '2'.charCodeAt(0)
8+
const C = 'C'.charCodeAt(0)
9+
const F = 'F'.charCodeAt(0)
10+
const J = 'J'.charCodeAt(0)
11+
const M = 'M'.charCodeAt(0)
12+
const P = 'P'.charCodeAt(0)
13+
const V = 'V'.charCodeAt(0)
14+
const c = 'c'.charCodeAt(0)
15+
const f = 'f'.charCodeAt(0)
16+
const j = 'j'.charCodeAt(0)
17+
const m = 'm'.charCodeAt(0)
18+
const p = 'p'.charCodeAt(0)
19+
const v = 'v'.charCodeAt(0)
20+
21+
return (n: number) => {
22+
if (n < 8) return n + two
23+
if (n === 8) return C
24+
if (n < 12) return n - 9 + F
25+
if (n === 12) return J
26+
if (n === 13) return M
27+
if (n < 17) return n - 14 + P
28+
if (n < 20) return n - 16 + V
29+
if (n === 20) return c
30+
if (n < 24) return n - 21 + f
31+
if (n === 24) return j
32+
if (n === 25) return m
33+
if (n < 29) return n - 26 + p
34+
if (n < 32) return n - 29 + v
35+
return NaN
36+
}
37+
}

src/lib/puid.spec.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ test('Safe32 (count non-power of 2 with carry)', (t) => {
200200
t.is(safe32Id(), '2nNB')
201201
})
202202

203-
test('puid safe32 entropyValues', (t) => {
203+
test('puid safe32', (t) => {
204204
const valuesBytes = fixedBytes([0xd2, 0xe3, 0xe9, 0xda, 0x19, 0x03, 0xb7, 0x3c])
205205
const entropyValues = (buf: Uint8Array) => buf.set(valuesBytes(buf.length))
206206

@@ -294,6 +294,20 @@ test('puid from AlphaUpper', (t) => {
294294
t.is(alphaUpperId(), 'AFM')
295295
})
296296

297+
test('puid from Base16', (t) => {
298+
const base16Bytes = fixedBytes([0xc7, 0xc9, 0x00, 0x2a, 0x16, 0x32])
299+
const base16Id = puidGenerator({
300+
bits: 12,
301+
chars: Chars.Base16,
302+
entropyBytes: base16Bytes
303+
})
304+
305+
t.is(base16Id(), 'C7C')
306+
t.is(base16Id(), '900')
307+
t.is(base16Id(), '2A1')
308+
t.is(base16Id(), '632')
309+
})
310+
297311
test('puid from Base32 chars (32 chars, 5 bits)', (t) => {
298312
const base32Bytes = fixedBytes([0xd2, 0xe3, 0xe9, 0xda, 0x19, 0x00, 0x22])
299313
const base32Id = puidGenerator({ bits: 46, chars: Chars.Base32, entropyBytes: base32Bytes })
@@ -323,6 +337,16 @@ test('puid from Base32HexUpper chars (32 chars, 5 bits)', (t) => {
323337
t.is(base32HexUpperId(), 'ERJ')
324338
})
325339

340+
test('puid Crockford32', (t) => {
341+
const valuesBytes = fixedBytes([0xd2, 0xe3, 0xe9, 0xda, 0x19, 0x03, 0xb7, 0x3c])
342+
const entropyValues = (buf: Uint8Array) => buf.set(valuesBytes(buf.length))
343+
344+
const valuesId = puidGenerator({ bits: 20, chars: Chars.Crockford32, entropyValues })
345+
t.is(valuesId(), 'TBHY')
346+
t.is(valuesId(), 'KPGS')
347+
t.is(valuesId(), '0EVK')
348+
})
349+
326350
test('puid from Decimal (10 chars)', (t) => {
327351
const decimalBytes = fixedBytes([0xd2, 0xe3, 0xe9, 0xda, 0x19, 0x03, 0xb7, 0x3c, 0xff])
328352
const decimalId = puidGenerator({ bits: 16, chars: Chars.Decimal, entropyBytes: decimalBytes })
@@ -439,6 +463,16 @@ test('puid from Symbol', (t) => {
439463
t.is(symbolId().length, length)
440464
})
441465

466+
test('puid WordSafe32', (t) => {
467+
const valuesBytes = fixedBytes([0xd2, 0xe3, 0xe9, 0xda, 0x19, 0x03, 0xb7, 0x3c])
468+
const entropyValues = (buf: Uint8Array) => buf.set(valuesBytes(buf.length))
469+
470+
const valuesId = puidGenerator({ bits: 20, chars: Chars.WordSafe32, entropyValues })
471+
t.is(valuesId(), 'pHVw')
472+
t.is(valuesId(), 'XgRm')
473+
t.is(valuesId(), '2PqX')
474+
})
475+
442476
test('Vowels (10 chars, 4 bits)', (t) => {
443477
const vowelBytes = fixedBytes([0xa6, 0x33, 0xf6, 0x9e, 0xbd, 0xee, 0xa7])
444478
//

0 commit comments

Comments
 (0)