diff --git a/ArrayBuffer/index.ts b/ArrayBuffer/index.ts index 3d538a6..3bf7cdd 100644 --- a/ArrayBuffer/index.ts +++ b/ArrayBuffer/index.ts @@ -41,6 +41,6 @@ export namespace ArrayBuffer { >(array: T): T export function random(length: number): Uint8Array export function random(array: ArrayBufferView | number): ArrayBufferView { - return crypto.getRandomValues(typeof array == "number" ? new Uint8Array(length) : array) + return crypto.getRandomValues(typeof array == "number" ? new Uint8Array(array) : array) } } diff --git a/Base64/index.spec.ts b/Base64/index.spec.ts index 6a786c2..79770fc 100644 --- a/Base64/index.spec.ts +++ b/Base64/index.spec.ts @@ -68,4 +68,8 @@ describe("Base64", () => { expect( cryptly.Base64.combine(["V6h7cyBpcyB0taUgZGF0YSAoK72", "Y4y5IGNhcm5hbCBwbGVhc3VyZ7u8"]) ).toMatchInlineSnapshot(`"ZJOykrzCrsfCykFfzZKF0JjS4D7z"`)) + it("next", () => + expect(cryptly.Base64.next("V6h7cyBpcyB0taUgZGF0YSAoK7z", 1, "ordered")).toMatchInlineSnapshot( + `"V6h7cyBpcyB0taUgZGF0YSAoK8-"` + )) }) diff --git a/Base64/index.ts b/Base64/index.ts index a625a43..d67df52 100644 --- a/Base64/index.ts +++ b/Base64/index.ts @@ -3,7 +3,6 @@ import { ArrayBuffer } from "../ArrayBuffer" import { Standard as Base64Standard } from "./Standard" export type Base64 = string - export namespace Base64 { export import Standard = Base64Standard export const type = isly.named("cryptly.Base64", isly.string(/^[A-Za-z0-9+/\-_=]*$/)) diff --git a/Encrypter/Aes/index.ts b/Encrypter/Aes/index.ts index 98b0c28..f2ac3f6 100644 --- a/Encrypter/Aes/index.ts +++ b/Encrypter/Aes/index.ts @@ -46,8 +46,7 @@ export class Aes { async export(parts?: number | Uint8Array | Uint8Array[] | Base64 | Base64[]): Promise { let result: Base64 | Base64[] const key = new Uint8Array(await crypto.subtle.exportKey("raw", await this.key)) - if (parts == undefined) - result = (await this.export(1))[0] + if (parts == undefined) result = (await this.export(1))[0] else if (typeof parts == "number") result = await this.export(parts > 1 ? Aes.generateRandomKeys(key.length, parts - 1) : []) else if (Base64.is(parts)) @@ -97,8 +96,7 @@ export class Aes { } private static reduceKeys(keys: Uint8Array[]): Uint8Array { const result = new Uint8Array(keys[0].length) - for (let index = 0; index < keys[0].length; index++) - result[index] = keys.reduce((p, c) => p ^ c[index], 0) + for (let index = 0; index < keys[0].length; index++) result[index] = keys.reduce((p, c) => p ^ c[index], 0) return result } } diff --git a/Identifier/Identifier16.spec.ts b/Identifier/Identifier16.spec.ts deleted file mode 100644 index 8ac7d4f..0000000 --- a/Identifier/Identifier16.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { cryptly } from "../index" - -describe("Identifier16", () => { - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(16))).toBeTruthy()) - it("generate length", () => expect(cryptly.Identifier.generate(16)).toHaveLength(16)) - it("fromHexadecimal length 24", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) - it.each([[1691418818480, /^---0XS0exv[\w\d-_]{6}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(16, "ordered", prefix)).toMatch(result) - ) - it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{5}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(16, "reversed", prefix)).toMatch(result) - ) - it("fromHexadecimal length 24", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) - it("toHexadecimal length 24", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvT")).toEqual("5d4282b672ed3c7738183bd3")) - it("fromHexadecimal length 23", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183bd")).toEqual("XUKCtnLtPHc4GDvQ")) - it("toHexadecimal length 23", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvQ", 23)).toEqual("5d4282b672ed3c7738183bd")) - it("fromHexadecimal length 22", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183b")).toEqual("XUKCtnLtPHc4GDs")) - it("toHexadecimal length 22", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDvs", 22)).toEqual("5d4282b672ed3c7738183b")) - it("fromHexadecimal length 21", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c7738183")).toEqual("XUKCtnLtPHc4GDA")) - it("toHexadecimal length 21", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GDA", 21)).toEqual("5d4282b672ed3c7738183")) - it("fromHexadecimal length 20", () => - expect(cryptly.Identifier.fromHexadecimal("5d4282b672ed3c773818")).toEqual("XUKCtnLtPHc4GA")) - it("toHexadecimal length 20", () => - expect(cryptly.Identifier.toHexadecimal("XUKCtnLtPHc4GA", 20)).toEqual("5d4282b672ed3c773818")) - - const time = 1691418818480 - it.each([ - [0, 1], - [1, 21111], - [2, 344546], - [3, 41112], - [4, 5434], - ])("order of ordered", (left, right) => - expect( - cryptly.Identifier.generate(16, "ordered", time + left) < cryptly.Identifier.generate(16, "ordered", time + right) - ).toEqual(true) - ) - it.each([ - [0, 1], - [0, 1666], - [1, 21111], - [2, 32323], - [3, 434], - ])("order of reversed", (left, right) => - expect( - cryptly.Identifier.generate(16, "reversed", time + left) > - cryptly.Identifier.generate(16, "reversed", time + right) - ).toEqual(true) - ) -}) diff --git a/Identifier/Identifier64.spec.ts b/Identifier/Identifier64.spec.ts deleted file mode 100644 index 143f107..0000000 --- a/Identifier/Identifier64.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { cryptly } from "../index" - -describe("Identifier64", () => { - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64))).toEqual(true)) - it("generate is length 64", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(64), 64)).toEqual(true)) - it("generate is not length 64", () => - expect(cryptly.Identifier.is(cryptly.Identifier.generate(32), 64)).toEqual(false)) - it("generate length", () => expect(cryptly.Identifier.generate(64)).toHaveLength(64)) - it.each([[1691418818480, /^---0XS0exv[\w\d-_]{54}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(64, "ordered", prefix)).toMatch(result) - ) - it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{53}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(64, "reversed", prefix)).toMatch(result) - ) - const data = [ - { - identifier: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", - binary: [ - 0, 16, 131, 16, 81, 135, 32, 146, 139, 48, 211, 143, 65, 20, 147, 81, 85, 151, 97, 150, 155, 113, 215, 159, 130, - 24, 163, 146, 89, 167, 162, 154, 171, 178, 219, 175, 195, 28, 179, 211, 93, 183, 227, 158, 187, 243, 223, 191, - ], - hexadecimal: "00108310518720928b30d38f41149351559761969b71d79f8218a39259a7a29aabb2dbafc31cb3d35db7e39ebbf3dfbf", - }, - ] - it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 64)).toBeTruthy()) - it.each(data)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) - ) - it.each(data)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) - ) - it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) - ) - it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) - ) - const time = 1691418818480 - it.each([ - [0, 1], - [1, 21111], - [2, 344546], - [3, 41112], - [4, 5434], - ])("order of ordered", (left, right) => - expect( - cryptly.Identifier.generate(64, "ordered", time + left) < cryptly.Identifier.generate(64, "ordered", time + right) - ).toEqual(true) - ) - it.each([ - [0, 1], - [0, 1666], - [1, 21111], - [2, 32323], - [3, 434], - ])("order of reversed", (left, right) => - expect( - cryptly.Identifier.generate(64, "reversed", time + left) > - cryptly.Identifier.generate(64, "reversed", time + right) - ).toEqual(true) - ) -}) diff --git a/Identifier/Length.spec.ts b/Identifier/Length.spec.ts new file mode 100644 index 0000000..3f23133 --- /dev/null +++ b/Identifier/Length.spec.ts @@ -0,0 +1,41 @@ +import { cryptly } from "../index" + +describe("Identifier.Length", () => { + it("bytes", () => + expect(cryptly.Identifier.Length.values.map(cryptly.Identifier.Length.bytes)).toMatchInlineSnapshot(` +[ + 3, + 6, + 9, + 12, + 15, + 18, + 21, + 24, + 27, + 30, + 33, + 36, + 39, + 42, + 45, + 48, + 51, + 54, + 57, + 60, + 63, + 66, + 69, + 72, + 75, + 78, + 81, + 84, + 87, + 90, + 93, + 96, +] +`)) +}) diff --git a/Identifier/Length.ts b/Identifier/Length.ts index 362db1c..e099f92 100644 --- a/Identifier/Length.ts +++ b/Identifier/Length.ts @@ -9,4 +9,7 @@ export namespace Length { export const type = isly.named("cryptly.Identifier.Length", isly.number(values as any as number[])) export const is = type.is export const flaw = type.flaw + export function bytes(length: Length): number { + return (length / 4) * 3 + } } diff --git a/Identifier/Standard.ts b/Identifier/Standard.ts new file mode 100644 index 0000000..467c46a --- /dev/null +++ b/Identifier/Standard.ts @@ -0,0 +1,10 @@ +import { isly } from "isly" + +export type Standard = typeof Standard.values[number] + +export namespace Standard { + export const values = ["url", "ordered", "reversed"] as const + export const type = isly.named("cryptly.Identifier.Standard", isly.string(values)) + export const is = type.is + export const flaw = type.flaw +} diff --git a/Identifier/index.ts b/Identifier/index.ts index bf77d7d..d0bf62b 100644 --- a/Identifier/index.ts +++ b/Identifier/index.ts @@ -1,100 +1,86 @@ import { isly } from "isly" +import { ArrayBuffer } from "../ArrayBuffer" +import { Base16 } from "../Base16" import { Base64 } from "../Base64" -import { crypto } from "../crypto" import { Length as IdentifierLength } from "./Length" +import { Standard as IdentifierStandard } from "./Standard" export type Identifier = string export namespace Identifier { export import Length = IdentifierLength + export import Standard = IdentifierStandard export const type = isly.named("cryptly.Identifier", isly.string(/^[a-zA-Z0-9\-_]{4,128}$/)) export function is(value: Identifier | any, length?: Length): value is Identifier { return type.is(value) && (length == undefined || value.length == length) } export const flaw = type.flaw - export function convert(identifier: Identifier, length: Length, standard?: Base64.Standard): Identifier - export function convert(identifier: Identifier, from: Base64.Standard, to?: Base64.Standard): Identifier + export function convert(identifier: Identifier, length: Length, standard?: Identifier.Standard): Identifier + export function convert(identifier: Identifier, from: Identifier.Standard, to?: Identifier.Standard): Identifier export function convert( identifier: Identifier, - length: Base64.Standard | Length, - standard: Base64.Standard = "url" + length: Identifier.Standard | Length, + standard: Identifier.Standard = "url" ): Identifier { - return Base64.Standard.is(length) + return Identifier.Standard.is(length) ? Base64.convert(identifier, length, standard) : identifier.length < length - ? min(length).slice(0, length - identifier.length) + identifier + ? min(length, standard).slice(0, length - identifier.length) + identifier : identifier.length > length ? identifier.slice(identifier.length - length) : identifier } - export function fromUint24(value: number): Identifier { - return fromHexadecimal(value.toString(16).padStart(6, "0")) + export function fromBase16(value: Base16, standard: Identifier.Standard = "url"): Identifier { + return fromBinary(Base16.decode(value), standard) } - export function toUint24(identifier: Identifier): number { - return Number.parseInt(toHexadecimal(identifier, 6), 16) + export function toBase16(identifier: Identifier, standard: Identifier.Standard = "url"): Base16 { + return Base16.encode(Base64.decode(identifier, standard)) } - export function fromUint48(value: number): Identifier { - return fromHexadecimal(value.toString(16).padStart(12, "0")) + export function fromUint24(value: number, standard: Identifier.Standard = "url"): Identifier { + return fromBase16(value.toString(16).padStart(6, "0"), standard) } - export function toUint48(identifier: Identifier): number { - return Number.parseInt(toHexadecimal(identifier, 12), 16) + export function toUint24(identifier: Identifier, standard: Identifier.Standard = "url"): number { + return Number.parseInt(toBase16(identifier, standard).slice(0, 6), 16) } - export function fromBinary(identifier: Uint8Array, standard: Base64.Standard = "url"): Identifier { + export function fromUint48(value: number, standard: Identifier.Standard = "url"): Identifier { + return fromBase16(value.toString(16).padStart(12, "0"), standard) + } + export function toUint48(identifier: Identifier, standard: Identifier.Standard = "url"): number { + return Number.parseInt(toBase16(identifier, standard).slice(0, 12), 16) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier { return Base64.encode(identifier, standard) } - export function toBinary(identifier: Identifier): Uint8Array { - return Base64.decode(identifier, "url") + export function toBinary(identifier: Identifier, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) } - export function generate(length: Length): Identifier - export function generate( - length: Length, - ordering: Extract, - value: number | Uint8Array | string - ): Identifier export function generate( length: Length, - ordering?: Extract, - value?: number | Uint8Array | string + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint ): Identifier { - let result: Identifier | undefined = undefined - if (value || value == 0 || value == "") - result = Base64.encode(value, ordering).substring(0, length) - return ( - (result ?? "") + - fromBinary(crypto.getRandomValues(new Uint8Array((length / 4) * 3)), ordering).substring( - 0, - length - (result ? result.length : 0) - ) - ) - } - export function fromHexadecimal(identifier: string): Identifier { - if (identifier.length % 2 == 1) - identifier += "0" - const result = new Uint8Array(identifier.length / 2) - for (let index = 0; index < result.length; index++) - result[index] = Number.parseInt(identifier[index * 2], 16) * 16 + Number.parseInt(identifier[index * 2 + 1], 16) - return fromBinary(result) - } - export function toHexadecimal(identifier: Identifier, length?: number): string { - const data = Base64.decode(identifier, "url") - let result: string[] = [] - for (const d of data) - result.push(Math.floor(d / 16).toString(16), (d % 16).toString(16)) - if (length) - result = result.slice(0, length) - return result.join("") + return !prefix + ? Base64.encode(ArrayBuffer.random(Identifier.Length.bytes(length)), standard) + : prefix instanceof Uint8Array || typeof prefix == "number" || typeof prefix == "bigint" + ? generate(length, standard, Base64.encode(prefix, standard)) + : prefix.length < length + ? prefix + generate(length, standard).slice(prefix.length) + : prefix.length > length + ? prefix.slice(0, length) + : prefix } - export function min(length: Length): Identifier { - return "".padStart(length, "-") + export function min(length: Length, standard: Identifier.Standard = "url"): Identifier { + const result = "".padStart(length, "-") + return standard != "ordered" ? convert(result, "ordered", standard) : result } - export function max(length: Length): Identifier { - return "".padStart(length, "z") + export function max(length: Length, standard: Identifier.Standard = "url"): Identifier { + const result = "".padStart(length, "z") + return standard != "ordered" ? convert(result, "ordered", standard) : result } - export function next(identifier: Identifier, increment = 1): Identifier { - const result = Base64.next(identifier, increment, "ordered") - return result.length == identifier.length ? result : result.substring(result.length - identifier.length) + export function next(identifier: Identifier, standard: Identifier.Standard = "url", increment = 1): Identifier { + return convert(Base64.next(identifier, increment, standard), Length.type.get(identifier) ?? 128, standard) } - export function previous(identifier: Identifier, decrement = 1): Identifier { - return next(identifier, -decrement) + export function previous(identifier: Identifier, standard: Identifier.Standard = "url", decrement = 1): Identifier { + return next(identifier, standard, -decrement) } } diff --git a/Identifier/Identifier12.spec.ts b/Identifier12/index.spec.ts similarity index 54% rename from Identifier/Identifier12.spec.ts rename to Identifier12/index.spec.ts index 7c093ae..53c2138 100644 --- a/Identifier/Identifier12.spec.ts +++ b/Identifier12/index.spec.ts @@ -1,8 +1,8 @@ import { cryptly } from "../index" describe("Identifier12", () => { - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) - it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) + it("generate is", () => expect(cryptly.Identifier12.is(cryptly.Identifier12.generate())).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier12.generate()).toHaveLength(12)) const data = [ { identifier: "abcdabcdabcd", @@ -18,25 +18,25 @@ describe("Identifier12", () => { }, { identifier: "AAAAAAAAAAAA", binary: [0, 0, 0, 0, 0, 0, 0, 0, 0], hexadecimal: "000000000000000000", value: 0 }, ] + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier12.is(identifier)).toBeTruthy()) it.each(data)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + expect(cryptly.Identifier12.toBinary(identifier)).toEqual(new Uint8Array(binary)) ) it.each(data)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + expect(cryptly.Identifier12.fromBinary(new Uint8Array(binary))).toEqual(identifier) ) - it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 12)).toBeTruthy()) - it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + it.each(data)(`toBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier12.toBase16(identifier)).toEqual(hexadecimal) ) - it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + it.each(data)(`fromBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier12.fromBase16(hexadecimal)).toEqual(identifier) ) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`HelloWorld${c}0`, 12)).toEqual(false)) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier12.is(`HelloWorld${c}0`)).toEqual(false)) it.each([[1691418818480, /^---0XS0exv[\w\d-_]{2}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(12, "ordered", prefix)).toMatch(result) + expect(cryptly.Identifier12.generate("ordered", prefix)).toMatch(result) ) it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{1}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(12, "reversed", prefix)).toMatch(result) + expect(cryptly.Identifier12.generate("reversed", prefix)).toMatch(result) ) const time = 1691418818480 it.each([ @@ -47,7 +47,7 @@ describe("Identifier12", () => { [4, 5434], ])("order of ordered", (left, right) => expect( - cryptly.Identifier.generate(12, "ordered", time + left) < cryptly.Identifier.generate(12, "ordered", time + right) + cryptly.Identifier12.generate("ordered", time + left) < cryptly.Identifier12.generate("ordered", time + right) ).toEqual(true) ) it.each([ @@ -58,8 +58,7 @@ describe("Identifier12", () => { [3, 434], ])("order of reversed", (left, right) => expect( - cryptly.Identifier.generate(12, "reversed", time + left) > - cryptly.Identifier.generate(12, "reversed", time + right) + cryptly.Identifier12.generate("reversed", time + left) > cryptly.Identifier12.generate("reversed", time + right) ).toEqual(true) ) }) diff --git a/Identifier12/index.ts b/Identifier12/index.ts new file mode 100644 index 0000000..8274c04 --- /dev/null +++ b/Identifier12/index.ts @@ -0,0 +1,57 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" +import { Identifier4 } from "../Identifier4" + +export type Identifier12 = string + +export namespace Identifier12 { + export const type = isly.named("cryptly.Identifier12", isly.string(/^[a-zA-Z0-9\-_]{12}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier12 { + const result = identifier.length != 12 ? Identifier.convert(identifier, 12, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier12 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier12, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier12 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier12, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier12 { + return Identifier.generate(12, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier12 { + const result = Identifier4.min(standard) + return result + result + result + } + export function max(standard: Identifier.Standard = "url"): Identifier12 { + const result = Identifier4.max(standard) + return result + result + result + } + export function next(identifier: Identifier12, standard: Identifier.Standard = "url", increment = 1): Identifier12 { + return convert(Base64.next(identifier, increment, standard), standard) + } + export function previous( + identifier: Identifier12, + standard: Identifier.Standard = "url", + decrement = 1 + ): Identifier12 { + return next(identifier, standard, -decrement) + } +} diff --git a/Identifier16/index.spec.ts b/Identifier16/index.spec.ts new file mode 100644 index 0000000..dae53f2 --- /dev/null +++ b/Identifier16/index.spec.ts @@ -0,0 +1,58 @@ +import { cryptly } from "../index" + +describe("Identifier16", () => { + it("generate is", () => expect(cryptly.Identifier16.is(cryptly.Identifier16.generate())).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier16.generate()).toHaveLength(16)) + it("fromBase16 length 24", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) + it.each([[1691418818480, /^---0XS0exv[\w\d-_]{6}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier16.generate("ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{5}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier16.generate("reversed", prefix)).toMatch(result) + ) + it("fromBase16 length 24", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c7738183bd3")).toEqual("XUKCtnLtPHc4GDvT")) + it("toBase16 length 24", () => + expect(cryptly.Identifier16.toBase16("XUKCtnLtPHc4GDvT")).toEqual("5d4282b672ed3c7738183bd3")) + it("fromBase16 length 23", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c7738183bd")).toEqual("XUKCtnLtPHc4GDvQ")) + it("toBase16 length 23", () => + expect(cryptly.Identifier16.toBase16("XUKCtnLtPHc4GDvQ").slice(0, 23)).toEqual("5d4282b672ed3c7738183bd")) + it("fromBase16 length 22", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c7738183b")).toEqual("AXUKCtnLtPHc4GDs")) + it("toBase16 length 22", () => + expect(cryptly.Identifier16.toBase16("XUKCtnLtPHc4GDvs").slice(0, 22)).toEqual("5d4282b672ed3c7738183b")) + it("fromBase16 length 21", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c7738183")).toEqual("AXUKCtnLtPHc4GDA")) + it("toBase16 length 21", () => + expect(cryptly.Identifier16.toBase16("XUKCtnLtPHc4GDA").slice(0, 21)).toEqual("5d4282b672ed3c7738183")) + it("fromBase16 length 20", () => + expect(cryptly.Identifier16.fromBase16("5d4282b672ed3c773818")).toEqual("AAXUKCtnLtPHc4GA")) + it("toBase16 length 20", () => + expect(cryptly.Identifier16.toBase16("XUKCtnLtPHc4GA").slice(0, 20)).toEqual("5d4282b672ed3c773818")) + + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier16.generate("ordered", time + left) < cryptly.Identifier16.generate("ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier16.generate("reversed", time + left) > cryptly.Identifier16.generate("reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier16/index.ts b/Identifier16/index.ts new file mode 100644 index 0000000..869345d --- /dev/null +++ b/Identifier16/index.ts @@ -0,0 +1,57 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" +import { Identifier8 } from "../Identifier8" + +export type Identifier16 = string + +export namespace Identifier16 { + export const type = isly.named("cryptly.Identifier16", isly.string(/^[a-zA-Z0-9\-_]{16}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier16 { + const result = identifier.length != 16 ? Identifier.convert(identifier, 16, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier16 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier16, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier16 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier16, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier16 { + return Identifier.generate(16, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier16 { + const result = Identifier8.min(standard) + return result + result + } + export function max(standard: Identifier.Standard = "url"): Identifier16 { + const result = Identifier8.max(standard) + return result + result + } + export function next(identifier: Identifier16, standard: Identifier.Standard = "url", increment = 1): Identifier16 { + return convert(Base64.next(identifier, increment, standard), standard) + } + export function previous( + identifier: Identifier16, + standard: Identifier.Standard = "url", + decrement = 1 + ): Identifier16 { + return next(identifier, standard, -decrement) + } +} diff --git a/Identifier20/index.spec.ts b/Identifier20/index.spec.ts new file mode 100644 index 0000000..325a826 --- /dev/null +++ b/Identifier20/index.spec.ts @@ -0,0 +1,59 @@ +import { cryptly } from "../index" + +describe("Identifier20", () => { + it("generate is", () => expect(cryptly.Identifier20.is(cryptly.Identifier20.generate())).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier20.generate()).toHaveLength(20)) + const data = [ + { + identifier: "abcdabcdabcdabcdabcd", + binary: [105, 183, 29, 105, 183, 29, 105, 183, 29, 105, 183, 29, 105, 183, 29], + hexadecimal: "69b71d69b71d69b71d69b71d69b71d", + }, + { + identifier: "____________________", + binary: [255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255], + hexadecimal: "ffffffffffffffffffffffffffffff", + }, + { + identifier: "AAAAAAAAAAAAAAAAAAAA", + binary: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + hexadecimal: "000000000000000000000000000000", + }, + ] + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier20.is(identifier)).toBeTruthy()) + it.each(data)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier20.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier20.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(data)(`toBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier20.toBase16(identifier)).toEqual(hexadecimal) + ) + it.each(data)(`fromBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier20.fromBase16(hexadecimal)).toEqual(identifier) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier20.generate("ordered", time + left) < cryptly.Identifier20.generate("ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier20.generate("reversed", time + left) > cryptly.Identifier20.generate("reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier20/index.ts b/Identifier20/index.ts new file mode 100644 index 0000000..b373d7c --- /dev/null +++ b/Identifier20/index.ts @@ -0,0 +1,57 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" +import { Identifier8 } from "../Identifier8" + +export type Identifier20 = string + +export namespace Identifier20 { + export const type = isly.named("cryptly.Identifier20", isly.string(/^[a-zA-Z0-9\-_]{20}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier20 { + const result = identifier.length != 20 ? Identifier.convert(identifier, 20, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier20 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier20, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier20 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier20, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier20 { + return Identifier.generate(20, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier20 { + const result = Identifier8.min(standard) + return result + result + result + result + result + } + export function max(standard: Identifier.Standard = "url"): Identifier20 { + const result = Identifier8.max(standard) + return result + result + result + result + result + } + export function next(identifier: Identifier20, standard: Identifier.Standard = "url", increment = 1): Identifier20 { + return convert(Base64.next(identifier, increment, standard), standard) + } + export function previous( + identifier: Identifier20, + standard: Identifier.Standard = "url", + decrement = 1 + ): Identifier20 { + return next(identifier, standard, -decrement) + } +} diff --git a/Identifier/Identifier4.spec.ts b/Identifier4/index.spec.ts similarity index 58% rename from Identifier/Identifier4.spec.ts rename to Identifier4/index.spec.ts index 1ba71e6..731f9ed 100644 --- a/Identifier/Identifier4.spec.ts +++ b/Identifier4/index.spec.ts @@ -1,19 +1,19 @@ import { cryptly } from "../index" describe("Identifier4", () => { - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(4), 4)).toEqual(true)) - it("generate length", () => expect(cryptly.Identifier.generate(4)).toHaveLength(4)) - it("min", () => expect(cryptly.Identifier.min(4)).toEqual("----")) - it("max", () => expect(cryptly.Identifier.max(4)).toEqual("zzzz")) - it.each(["ordered", "reversed"] as const)("generate is %s", standard => - expect(cryptly.Identifier.is(cryptly.Identifier.generate(4, standard, ""), 4)).toEqual(true) + it("generate is", () => expect(cryptly.Identifier4.is(cryptly.Identifier4.generate())).toEqual(true)) + it("generate length", () => expect(cryptly.Identifier4.generate()).toHaveLength(4)) + it("min", () => expect(cryptly.Identifier4.min("ordered")).toEqual("----")) + it("max", () => expect(cryptly.Identifier4.max("ordered")).toEqual("zzzz")) + it.each(["url", "ordered", "reversed"] as const)("generate is %s", standard => + expect(cryptly.Identifier4.is(cryptly.Identifier4.generate(standard))).toEqual(true) ) const data = [ { identifier: "abcd", binary: [105, 183, 29], hexadecimal: "69b71d", - value: 6928157, + value: 6_928_157, ordered: "PQRS", reversed: "_ZYX", next: "PQRT", @@ -23,7 +23,7 @@ describe("Identifier4", () => { identifier: "test", binary: [181, 235, 45], hexadecimal: "b5eb2d", - value: 11922221, + value: 11_922_221, ordered: "hTgh", reversed: "HWIH", next: "hTgi", @@ -33,7 +33,7 @@ describe("Identifier4", () => { identifier: "DEMO", binary: [12, 67, 14], hexadecimal: "0c430e", - value: 803598, + value: 803_598, ordered: "23BD", reversed: "wvnl", next: "23BE", @@ -43,7 +43,7 @@ describe("Identifier4", () => { identifier: "TEST", binary: [76, 68, 147], hexadecimal: "4c4493", - value: 4998291, + value: 4_998_291, ordered: "I3HI", reversed: "gvhg", next: "I3HJ", @@ -53,7 +53,7 @@ describe("Identifier4", () => { identifier: "____", binary: [255, 255, 255], hexadecimal: "ffffff", - value: 16777215, + value: 16_777_215, ordered: "zzzz", reversed: "----", next: "----", @@ -63,7 +63,7 @@ describe("Identifier4", () => { identifier: "zzzz", binary: [207, 60, 243], hexadecimal: "cf3cf3", - value: 13581555, + value: 13_581_555, ordered: "nnnn", reversed: "BBBB", next: "nnno", @@ -83,7 +83,7 @@ describe("Identifier4", () => { identifier: "aAzZ", binary: [104, 12, 217], hexadecimal: "680cd9", - value: 6819033, + value: 6_819_033, ordered: "P-nO", reversed: "_zBa", next: "P-nP", @@ -93,7 +93,7 @@ describe("Identifier4", () => { identifier: "demo", binary: [117, 233, 168], hexadecimal: "75e9a8", - value: 7727528, + value: 7_727_528, ordered: "STac", reversed: "XWOM", next: "STad", @@ -103,7 +103,7 @@ describe("Identifier4", () => { identifier: "GX_K", binary: [25, 127, 202], hexadecimal: "197fca", - value: 1671114, + value: 1_671_114, ordered: "5Mz9", reversed: "tc-p", next: "5MzA", @@ -113,7 +113,7 @@ describe("Identifier4", () => { identifier: "GDvT", binary: [24, 59, 211], hexadecimal: "183bd3", - value: 1588179, + value: 1_588_179, ordered: "52jI", reversed: "twFg", next: "52jJ", @@ -123,7 +123,7 @@ describe("Identifier4", () => { identifier: "tgAg", binary: [182, 0, 32], hexadecimal: "b60020", - value: 11927584, + value: 11_927_584, ordered: "hV-V", reversed: "HUzU", next: "hV-W", @@ -133,7 +133,7 @@ describe("Identifier4", () => { identifier: "LeeT", binary: [45, 231, 147], hexadecimal: "2de793", - value: 3008403, + value: 3_008_403, ordered: "ATTI", reversed: "oWWg", next: "ATTJ", @@ -153,60 +153,59 @@ describe("Identifier4", () => { identifier: "___A", binary: [255, 255, 192], hexadecimal: "ffffc0", - value: 16777152, + value: 16_777_152, ordered: "zzz-", reversed: "---z", next: "zzz0", previous: "zzyz", }, ] + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier4.is(identifier)).toBeTruthy()) it.each(data)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + expect(cryptly.Identifier4.toBinary(identifier)).toEqual(new Uint8Array(binary)) ) it.each(data)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + expect(cryptly.Identifier4.fromBinary(new Uint8Array(binary))).toEqual(identifier) ) - it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 4)).toBeTruthy()) - - it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + it.each(data)(`toBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier4.toBase16(identifier)).toEqual(hexadecimal) ) - it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + it.each(data)(`fromBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier4.fromBase16(hexadecimal)).toEqual(identifier) ) it.each(data)(`toUint24 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.toUint24(identifier)).toEqual(value) + expect(cryptly.Identifier4.toUint24(identifier)).toEqual(value) ) it.each(data)(`fromUint24 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.fromUint24(value)).toEqual(identifier) + expect(cryptly.Identifier4.fromUint24(value)).toEqual(identifier) ) it.each(data)(`ordered %s`, ({ identifier, ordered }) => - expect(cryptly.Identifier.convert(identifier, "url", "ordered")).toEqual(ordered) + expect(cryptly.Identifier4.convert(identifier, "url", "ordered")).toEqual(ordered) ) it.each(data)(`reversed %s`, ({ identifier, reversed }) => - expect(cryptly.Identifier.convert(identifier, "url", "reversed")).toEqual(reversed) + expect(cryptly.Identifier4.convert(identifier, "url", "reversed")).toEqual(reversed) ) - it.each(data)(`next %s`, ({ ordered, next }) => expect(cryptly.Identifier.next(ordered)).toEqual(next)) + it.each(data)(`next %s`, ({ ordered, next }) => expect(cryptly.Identifier4.next(ordered, "ordered")).toEqual(next)) it.each(data)(`previous %s`, ({ ordered, previous }) => - expect(cryptly.Identifier.previous(ordered)).toEqual(previous) - ) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`He${c}0`, 4)).toEqual(false)) - it.each([[1691418818480, /^---0$/]])(`generate ordered w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(4, "ordered", prefix)).toMatch(result) + expect(cryptly.Identifier4.previous(ordered, "ordered")).toEqual(previous) ) - it.each([[1691418818480, /^zzzy$/]])(`generate reversed w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(4, "reversed", prefix)).toMatch(result) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier4.is(`He${c}0`)).toEqual(false)) + it.each([ + ["ordered", 1_691_418_818_480, /^---0$/], + ["reversed", 1_691_418_818_480, /^zzzy$/], + ] as const)(`generate ordered w/ prefix %s`, (standard, prefix, result) => + expect(cryptly.Identifier4.generate(standard, prefix)).toMatch(result) ) - const time = 1691418818480 + const time = 1_691_418_818_480 it.each([ [0, 1], [1, 21111], [2, 344546], [3, 41112], [4, 5434], - ])("order of ordered", (left, right) => + ])("order of ordered %i <= %i", (left, right) => expect( - cryptly.Identifier.generate(4, "ordered", time + left) <= cryptly.Identifier.generate(4, "ordered", time + right) + cryptly.Identifier4.generate("ordered", time + left) <= cryptly.Identifier4.generate("ordered", time + right) ).toEqual(true) ) it.each([ @@ -215,10 +214,9 @@ describe("Identifier4", () => { [1, 21111], [2, 32323], [3, 434], - ])("order of reversed", (left, right) => + ])("order of reversed %i >= %i", (left, right) => expect( - cryptly.Identifier.generate(4, "reversed", time + left) >= - cryptly.Identifier.generate(4, "reversed", time + right) + cryptly.Identifier4.generate("reversed", time + left) >= cryptly.Identifier4.generate("reversed", time + right) ).toEqual(true) ) }) diff --git a/Identifier4/index.ts b/Identifier4/index.ts new file mode 100644 index 0000000..a352900 --- /dev/null +++ b/Identifier4/index.ts @@ -0,0 +1,57 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" + +export type Identifier4 = string + +export namespace Identifier4 { + export const type = isly.named("cryptly.Identifier4", isly.string(/^[a-zA-Z0-9\-_]{4}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier4 { + const result = identifier.length != 4 ? Identifier.convert(identifier, 4, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier4 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier4, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier4 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier4, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function fromUint24(value: number, standard: Identifier.Standard = "url"): Identifier4 { + return fromBase16(value.toString(16).padStart(6, "0"), standard) + } + export function toUint24(identifier: Identifier4, standard: Identifier.Standard = "url"): number { + return Number.parseInt(toBase16(identifier, standard).slice(0, 6), 16) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier4 { + return Identifier.generate(4, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier4 { + return fromUint24(0, standard) + } + export function max(standard: Identifier.Standard = "url"): Identifier4 { + return fromUint24(16_777_215, standard) + } + export function next(identifier: Identifier4, standard: Identifier.Standard = "url", increment = 1): Identifier4 { + const result = Base64.next(identifier, increment, standard) + return convert(result, standard) + } + export function previous(identifier: Identifier4, standard: Identifier.Standard = "url", decrement = 1): Identifier4 { + return next(identifier, standard, -decrement) + } +} diff --git a/Identifier64/index.spec.ts b/Identifier64/index.spec.ts new file mode 100644 index 0000000..7ffff13 --- /dev/null +++ b/Identifier64/index.spec.ts @@ -0,0 +1,60 @@ +import { cryptly } from "../index" + +describe("Identifier64", () => { + it("generate is", () => expect(cryptly.Identifier64.is(cryptly.Identifier64.generate())).toEqual(true)) + it("generate is length 64", () => expect(cryptly.Identifier64.is(cryptly.Identifier64.generate())).toEqual(true)) + it("generate is not length 64", () => expect(cryptly.Identifier64.is(cryptly.Identifier.generate(32))).toEqual(false)) + it("generate length", () => expect(cryptly.Identifier64.generate()).toHaveLength(64)) + it.each([[1691418818480, /^---0XS0exv[\w\d-_]{54}$/]])(`generate ordered w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier64.generate("ordered", prefix)).toMatch(result) + ) + it.each([[1691418818480, /^zzzySXyK13z[\w\d-_]{53}$/]])(`generate reversed w/ prefix %s`, (prefix, result) => + expect(cryptly.Identifier64.generate("reversed", prefix)).toMatch(result) + ) + const data = [ + { + identifier: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", + binary: [ + 0, 16, 131, 16, 81, 135, 32, 146, 139, 48, 211, 143, 65, 20, 147, 81, 85, 151, 97, 150, 155, 113, 215, 159, 130, + 24, 163, 146, 89, 167, 162, 154, 171, 178, 219, 175, 195, 28, 179, 211, 93, 183, 227, 158, 187, 243, 223, 191, + ], + hexadecimal: "00108310518720928b30d38f41149351559761969b71d79f8218a39259a7a29aabb2dbafc31cb3d35db7e39ebbf3dfbf", + }, + ] + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier64.is(identifier)).toBeTruthy()) + it.each(data)(`toBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier64.toBinary(identifier)).toEqual(new Uint8Array(binary)) + ) + it.each(data)(`fromBinary %s`, ({ identifier, binary }) => + expect(cryptly.Identifier64.fromBinary(new Uint8Array(binary))).toEqual(identifier) + ) + it.each(data)(`toBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier64.toBase16(identifier)).toEqual(hexadecimal) + ) + it.each(data)(`fromBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier64.fromBase16(hexadecimal)).toEqual(identifier) + ) + const time = 1691418818480 + it.each([ + [0, 1], + [1, 21111], + [2, 344546], + [3, 41112], + [4, 5434], + ])("order of ordered", (left, right) => + expect( + cryptly.Identifier64.generate("ordered", time + left) < cryptly.Identifier64.generate("ordered", time + right) + ).toEqual(true) + ) + it.each([ + [0, 1], + [0, 1666], + [1, 21111], + [2, 32323], + [3, 434], + ])("order of reversed", (left, right) => + expect( + cryptly.Identifier64.generate("reversed", time + left) > cryptly.Identifier64.generate("reversed", time + right) + ).toEqual(true) + ) +}) diff --git a/Identifier64/index.ts b/Identifier64/index.ts new file mode 100644 index 0000000..619aa2d --- /dev/null +++ b/Identifier64/index.ts @@ -0,0 +1,57 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" +import { Identifier8 } from "../Identifier8" + +export type Identifier64 = string + +export namespace Identifier64 { + export const type = isly.named("cryptly.Identifier64", isly.string(/^[a-zA-Z0-9\-_]{64}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier64 { + const result = identifier.length != 64 ? Identifier.convert(identifier, 64, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier64 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier64, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier64 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier64, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier64 { + return Identifier.generate(64, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier64 { + const result = Identifier8.min(standard) + return result + result + result + result + result + result + result + result + } + export function max(standard: Identifier.Standard = "url"): Identifier64 { + const result = Identifier8.max(standard) + return result + result + result + result + result + result + result + result + } + export function next(identifier: Identifier64, standard: Identifier.Standard = "url", increment = 1): Identifier64 { + return convert(Base64.next(identifier, increment, standard), standard) + } + export function previous( + identifier: Identifier64, + standard: Identifier.Standard = "url", + decrement = 1 + ): Identifier64 { + return next(identifier, standard, -decrement) + } +} diff --git a/Identifier/Identifier8.spec.ts b/Identifier8/index.spec.ts similarity index 67% rename from Identifier/Identifier8.spec.ts rename to Identifier8/index.spec.ts index e720ce7..c160daa 100644 --- a/Identifier/Identifier8.spec.ts +++ b/Identifier8/index.spec.ts @@ -1,8 +1,8 @@ import { cryptly } from "../index" describe("Identifier8", () => { - it("generate is", () => expect(cryptly.Identifier.is(cryptly.Identifier.generate(8))).toBeTruthy()) - it("generate length", () => expect(cryptly.Identifier.generate(8)).toHaveLength(8)) + it("generate is", () => expect(cryptly.Identifier8.is(cryptly.Identifier8.generate())).toBeTruthy()) + it("generate length", () => expect(cryptly.Identifier8.generate()).toHaveLength(8)) const data = [ { identifier: "abcdabcd", @@ -58,31 +58,31 @@ describe("Identifier8", () => { value: 50472629954451, }, ] - it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier.is(identifier, 8)).toBeTruthy()) + it.each(data)(`is %s`, ({ identifier }) => expect(cryptly.Identifier8.is(identifier)).toBeTruthy()) it.each(data)(`toBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.toBinary(identifier)).toEqual(new Uint8Array(binary)) + expect(cryptly.Identifier8.toBinary(identifier)).toEqual(new Uint8Array(binary)) ) it.each(data)(`fromBinary %s`, ({ identifier, binary }) => - expect(cryptly.Identifier.fromBinary(new Uint8Array(binary))).toEqual(identifier) + expect(cryptly.Identifier8.fromBinary(new Uint8Array(binary))).toEqual(identifier) ) - it.each(data)(`toHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.toHexadecimal(identifier)).toEqual(hexadecimal) + it.each(data)(`toBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier8.toBase16(identifier)).toEqual(hexadecimal) ) - it.each(data)(`fromHexadecimal %s`, ({ identifier, hexadecimal }) => - expect(cryptly.Identifier.fromHexadecimal(hexadecimal)).toEqual(identifier) + it.each(data)(`fromBase16 %s`, ({ identifier, hexadecimal }) => + expect(cryptly.Identifier8.fromBase16(hexadecimal)).toEqual(identifier) ) it.each(data)(`toUint48 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.toUint48(identifier)).toEqual(value) + expect(cryptly.Identifier8.toUint48(identifier)).toEqual(value) ) it.each(data)(`fromUint48 %s`, ({ identifier, value }) => - expect(cryptly.Identifier.fromUint48(value)).toEqual(identifier) + expect(cryptly.Identifier8.fromUint48(value)).toEqual(identifier) ) - it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier.is(`Hello${c}01`, 8)).toEqual(false)) + it.each(["!", "/", "=", "."])("is not $s", c => expect(cryptly.Identifier8.is(`Hello${c}01`)).toEqual(false)) it.each([[1691418818480, /^---0XS0e$/]])(`generate ordered w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(8, "ordered", prefix)).toMatch(result) + expect(cryptly.Identifier8.generate("ordered", prefix)).toMatch(result) ) it.each([[1691418818480, /^zzzySXyK$/]])(`generate reversed w/ prefix %s`, (prefix, result) => - expect(cryptly.Identifier.generate(8, "reversed", prefix)).toMatch(result) + expect(cryptly.Identifier8.generate("reversed", prefix)).toMatch(result) ) const time = 1691418818480 it.each([ @@ -93,7 +93,7 @@ describe("Identifier8", () => { [4, 5434], ])("order of ordered", (left, right) => expect( - cryptly.Identifier.generate(8, "ordered", time + left) <= cryptly.Identifier.generate(8, "ordered", time + right) + cryptly.Identifier8.generate("ordered", time + left) <= cryptly.Identifier8.generate("ordered", time + right) ).toEqual(true) ) it.each([ @@ -104,8 +104,7 @@ describe("Identifier8", () => { [3, 434], ])("order of reversed", (left, right) => expect( - cryptly.Identifier.generate(8, "reversed", time + left) >= - cryptly.Identifier.generate(8, "reversed", time + right) + cryptly.Identifier8.generate("reversed", time + left) >= cryptly.Identifier8.generate("reversed", time + right) ).toEqual(true) ) }) diff --git a/Identifier8/index.ts b/Identifier8/index.ts new file mode 100644 index 0000000..ea880d0 --- /dev/null +++ b/Identifier8/index.ts @@ -0,0 +1,56 @@ +import { isly } from "isly" +import type { Base16 } from "../Base16" +import { Base64 } from "../Base64" +import { Identifier } from "../Identifier" + +export type Identifier8 = string + +export namespace Identifier8 { + export const type = isly.named("cryptly.Identifier8", isly.string(/^[a-zA-Z0-9\-_]{8}$/)) + export const is = type.is + export const flaw = type.flaw + export function convert( + identifier: Identifier, + from: Identifier.Standard = "url", + to?: Identifier.Standard + ): Identifier8 { + const result = identifier.length != 8 ? Identifier.convert(identifier, 8, from) : identifier + return to && from != to ? Base64.convert(result, from, to) : result + } + export function fromBase16(identifier: Base16, standard: Identifier.Standard = "url"): Identifier8 { + return convert(Identifier.fromBase16(identifier, standard)) + } + export function toBase16(identifier: Identifier8, standard: Identifier.Standard = "url"): string { + return Identifier.toBase16(identifier, standard) + } + export function fromBinary(identifier: Uint8Array, standard: Identifier.Standard = "url"): Identifier8 { + return convert(Base64.encode(identifier, standard)) + } + export function toBinary(identifier: Identifier8, standard: Identifier.Standard = "url"): Uint8Array { + return Base64.decode(identifier, standard) + } + export function fromUint48(value: number, standard: Identifier.Standard = "url"): Identifier8 { + return fromBase16(value.toString(16).padStart(12, "0"), standard) + } + export function toUint48(identifier: Identifier8, standard: Identifier.Standard = "url"): number { + return Number.parseInt(toBase16(identifier, standard).slice(0, 12), 16) + } + export function generate( + standard: Identifier.Standard = "url", + prefix?: Base64 | Uint8Array | number | bigint + ): Identifier8 { + return Identifier.generate(8, standard, prefix) + } + export function min(standard: Identifier.Standard = "url"): Identifier8 { + return fromUint48(0, standard) + } + export function max(standard: Identifier.Standard = "url"): Identifier8 { + return fromUint48(281_474_976_710_655, standard) + } + export function next(identifier: Identifier8, standard: Identifier.Standard = "url", increment = 1): Identifier8 { + return convert(Base64.next(identifier, increment, standard), standard) + } + export function previous(identifier: Identifier8, standard: Identifier.Standard = "url", decrement = 1): Identifier8 { + return next(identifier, standard, -decrement) + } +} diff --git a/index.ts b/index.ts index da8536e..ad37505 100644 --- a/index.ts +++ b/index.ts @@ -6,6 +6,12 @@ import { Digester as cryptlyDigester } from "./Digester" import { Encrypter as cryptlyEncrypter } from "./Encrypter" import { Encrypters as cryptlyEncrypters } from "./Encrypters" import { Identifier as cryptlyIdentifier } from "./Identifier" +import { Identifier4 as cryptlyIdentifier4 } from "./Identifier4" +import { Identifier8 as cryptlyIdentifier8 } from "./Identifier8" +import { Identifier12 as cryptlyIdentifier12 } from "./Identifier12" +import { Identifier16 as cryptlyIdentifier16 } from "./Identifier16" +import { Identifier20 as cryptlyIdentifier20 } from "./Identifier20" +import { Identifier64 as cryptlyIdentifier64 } from "./Identifier64" import { Otp as cryptlyOtp } from "./Otp" import { Password as cryptlyPassword } from "./Password" import { Signer as cryptlySigner } from "./Signer" @@ -19,7 +25,13 @@ export namespace cryptly { export import Encrypter = cryptlyEncrypter export import Encrypters = cryptlyEncrypters export import Identifier = cryptlyIdentifier + export import Identifier4 = cryptlyIdentifier4 + export import Identifier8 = cryptlyIdentifier8 + export import Identifier12 = cryptlyIdentifier12 + export import Identifier16 = cryptlyIdentifier16 + export import Identifier20 = cryptlyIdentifier20 + export import Identifier64 = cryptlyIdentifier64 + export import Otp = cryptlyOtp export import Password = cryptlyPassword export import Signer = cryptlySigner - export import Otp = cryptlyOtp }