Skip to content

Commit

Permalink
Add support for string values
Browse files Browse the repository at this point in the history
  • Loading branch information
turiyadev committed Jan 10, 2024
1 parent 72a3a20 commit 29bb2be
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 24 deletions.
23 changes: 16 additions & 7 deletions README.md
Expand Up @@ -56,7 +56,7 @@ the value.
type KeymaskOptions = {
seed?: ArrayBuffer;
size?: number | number[];
type?: "number" | "bigint" | "integer" | "buffer";
type?: "number" | "bigint" | "string" | "integer" | "buffer";
encoder?: KeymaskEncoder;
};
```
Expand Down Expand Up @@ -85,6 +85,11 @@ const unmask4 = kaymask.unmask("NpRcJcFtscDkyxmQkD");
// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] as ArrayBuffer
```

Since `v0.9.2`, the input value to the `mask` function can also be provided as
a `string`. This will be converted internally to a `BigInt` and treated as a
numeric value. For example, in the above example `keymask.mask("123456789")`
would also mask to `"wMjMGR"`.

### `seed`

If a `seed` value is provided, it will be used to initialize LCG offsets
Expand All @@ -96,10 +101,10 @@ bytes long depending on whether a preconfigured `KeymaskEncoder` is used (see
`encoder` option below). When no `encoder` is provided, the full `32` bytes
are required.

Providing a randomized `seed` is highly recommended, as this makes the mappings
between inputs and outputs highly unpredictable when the `seed` is kept secret.
The `seed` should generally not change for the lifetime of your application
(doing so would make it impossible to unmask previously masked values).
Providing a randomized `seed` is generally recommended, as this makes the
mappings between inputs and outputs highly unpredictable. However, the `seed`
should typically not change for the lifetime of your application, as this would
render it impossible to unmask previously masked values.

**Example (Seeded)**

Expand Down Expand Up @@ -191,7 +196,7 @@ anything longer than 12 characters (=64 bits) will be returned as an
supplied keymask will be, the return type is a union type:

```TypeScript
type KeymaskData = number | bigint | ArrayBuffer;
type KeymaskData = number | bigint | string | ArrayBuffer;
```

There may very well be times when you know the expected return type in advance,
Expand All @@ -203,6 +208,8 @@ provided, it must conform to one of the following strings:
type conversion is done, so be sure to only use this with short keymasks).
- `"bigint"` The result will be converted to a `BigInt` regardless of its
magnitude.
- `"string"` The result will be converted to a `BigInt` then cast to a `string`
regardless of its magnitude.
- `"integer"` Similar to the default behaviour, but values larger than 64 bits
will be returned as a `BigInt` rather than an `ArrayBuffer`.
- `"buffer"` The result will be converted to an `ArrayBuffer` regardless of its
Expand All @@ -220,12 +227,14 @@ import { Keymask } from "keymask";
const defaultKeymask = new Keymask();
const numberKeymask = new Keymask({ type: "number" });
const bigintKeymask = new Keymask({ type: "bigint" });
const stringKeymask = new Keymask({ type: "string" });
const bufferKeymask = new Keymask({ type: "buffer" });

const unmask1 = defaultKeymask.unmask("GVSYBp"); // 123456789 as KeymaskData
const unmask2 = numberKeymask.unmask("GVSYBp"); // 123456789 as number
const unmask3 = bigintKeymask.unmask("GVSYBp"); // 123456789n as bigint
const unmask4 = bufferKeymask.unmask("GVSYBp");
const unmask4 = stringKeymask.unmask("GVSYBp"); // "123456789"
const unmask5 = bufferKeymask.unmask("GVSYBp");
// [21, 205, 91, 7, 0, 0, 0, 0] as ArrayBuffer
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "keymask",
"version": "0.9.1",
"version": "0.9.2",
"description": "Map sequential IDs or serial numbers to random-looking strings",
"type": "module",
"exports": {
Expand Down
41 changes: 25 additions & 16 deletions src/Keymask.ts
Expand Up @@ -46,20 +46,17 @@ function encodingLength(value: number | bigint, sizes: number[]): number {
return length;
}

export type KeymaskData = number | bigint | ArrayBuffer;
export type KeymaskData = number | bigint | string | ArrayBuffer;

export type KeymaskType = "number" | "bigint" | "integer" | "buffer" | undefined;
export type KeymaskType = "number" | "bigint" | "string" | "integer" | "buffer" | undefined;

export type KeymaskValue<T extends KeymaskType> =
T extends "number" ?
number :
T extends "bigint" ?
bigint :
T extends "integer" ?
number | bigint :
T extends "buffer" ?
ArrayBuffer :
KeymaskData;
T extends "number" ? number
: T extends "bigint" ? bigint
: T extends "string" ? string
: T extends "integer" ? number | bigint
: T extends "buffer" ? ArrayBuffer
: KeymaskData;

export type KeymaskOptions<T extends KeymaskType> = {
seed?: ArrayBuffer;
Expand Down Expand Up @@ -122,7 +119,7 @@ export class Keymask<T extends KeymaskType> {

/**
* Mask the provided value.
* @param {number | bigint | ArrayBuffer} value The value to mask.
* @param {number | bigint | string | ArrayBuffer} value The value to mask.
* @returns {string} The masked value.
*/
mask(value: KeymaskData): string {
Expand All @@ -144,6 +141,9 @@ export class Keymask<T extends KeymaskType> {
}
return this.encoder.encode(data.buffer, length);
} else {
if (typeof value === "string") {
value = BigInt(value);
}
length = encodingLength(value, this.sizes);
return this.encoder.encode(
this.generator.next(value, length),
Expand All @@ -155,7 +155,7 @@ export class Keymask<T extends KeymaskType> {
/**
* Unmask the provided value.
* @param {string} value The encoded value to unmask.
* @returns {number | bigint | ArrayBuffer} The unmasked value.
* @returns {number | bigint | string | ArrayBuffer} The unmasked value.
*/
unmask(value: string): KeymaskValue<T> {
const result = this.encoder.decode(value);
Expand All @@ -173,8 +173,13 @@ export class Keymask<T extends KeymaskType> {
true
);
}
if (this.type === "bigint" || this.type === "integer" && value.length > 10) {
return toBigInt(data) as KeymaskValue<T>;
if (
this.type === "bigint" ||
this.type === "string" ||
this.type === "integer" && value.length > 10
) {
const n = toBigInt(data);
return (this.type === "string" ? n.toString() : n) as KeymaskValue<T>;
}
if (length < 12) {
const bytes = new Uint8Array(result);
Expand All @@ -187,6 +192,10 @@ export class Keymask<T extends KeymaskType> {
return result as KeymaskValue<T>;
}
const n = this.generator.previous(result, value.length, this.type === "bigint");
return (this.type === "buffer" ? toBuffer(n) : n) as KeymaskValue<T>;
return (
this.type === "buffer" ? toBuffer(n)
: this.type === "string" ? n.toString()
: n
) as KeymaskValue<T>;
}
}
97 changes: 97 additions & 0 deletions test/Keymask.test.ts
Expand Up @@ -293,6 +293,103 @@ describe("Keymask", () => {
});
});

describe("String output", () => {
const keymask = new Keymask({ type: "string" });

it("should mask and unmask in range 1", () => {
equal(keymask.mask("1"), "c");
equal(keymask.mask("40"), "Y");
equal(keymask.unmask("c"), "1");
equal(keymask.unmask("Y"), "40");
});

it("should mask and unmask in range 2", () => {
equal(keymask.mask("41"), "PK");
equal(keymask.mask("1020"), "sV");
equal(keymask.unmask("PK"), "41");
equal(keymask.unmask("sV"), "1020");
});

it("should mask and unmask in range 3", () => {
equal(keymask.mask("1021"), "Lfc");
equal(keymask.mask("65520"), "dhk");
equal(keymask.unmask("Lfc"), "1021");
equal(keymask.unmask("dhk"), "65520");
});

it("should mask and unmask in range 4", () => {
equal(keymask.mask("65521"), "NcPL");
equal(keymask.mask("2097142"), "NzPT");
equal(keymask.unmask("NcPL"), "65521");
equal(keymask.unmask("NzPT"), "2097142");
});

it("should mask and unmask in range 5", () => {
equal(keymask.mask("2097143"), "bWGJC");
equal(keymask.mask("67108858"), "dnBsV");
equal(keymask.unmask("bWGJC"), "2097143");
equal(keymask.unmask("dnBsV"), "67108858");
});

it("should mask and unmask in range 6", () => {
equal(keymask.mask("67108859"), "WkCBvr");
equal(keymask.mask("4294967290"), "mSJnSd");
equal(keymask.unmask("WkCBvr"), "67108859");
equal(keymask.unmask("mSJnSd"), "4294967290");
});

it("should mask and unmask in range 7", () => {
equal(keymask.mask("4294967291"), "ncbyPTV");
equal(keymask.mask("137438953446"), "mGJFsQc");
equal(keymask.unmask("ncbyPTV"), "4294967291");
equal(keymask.unmask("mGJFsQc"), "137438953446");
});

it("should mask and unmask in range 8", () => {
equal(keymask.mask("137438953447"), "vwmKZxKZ");
equal(keymask.mask("4398046511092"), "GwdjRScK");
equal(keymask.unmask("vwmKZxKZ"), "137438953447");
equal(keymask.unmask("GwdjRScK"), "4398046511092");
});

it("should mask and unmask in range 9", () => {
equal(keymask.mask("4398046511093"), "gqFHjWmxF");
equal(keymask.mask("281474976710596"), "wZVHVzvrj");
equal(keymask.unmask("gqFHjWmxF"), "4398046511093");
equal(keymask.unmask("wZVHVzvrj"), "281474976710596");
});

it("should mask and unmask in range 10", () => {
equal(keymask.mask("281474976710597"), "nWRWYwnkhD");
equal(keymask.mask("9007199254740880"), "KdCvLBSKJb");
equal(keymask.unmask("nWRWYwnkhD"), "281474976710597");
equal(keymask.unmask("KdCvLBSKJb"), "9007199254740880");
});

it("should mask and unmask in range 11", () => {
equal(keymask.mask("9007199254740881"), "NjQkwmfKKVP");
equal(keymask.mask("288230376151711716"), "TQmxMJKgrNW");
equal(keymask.unmask("NjQkwmfKKVP"), "9007199254740881");
equal(keymask.unmask("TQmxMJKgrNW"), "288230376151711716");
});

it("should mask and unmask in range 12", () => {
equal(keymask.mask("288230376151711717"), "DjfkCZLtcBLn");
equal(keymask.mask("18446744073709551556"), "YcWfgzxKYXFW");
equal(keymask.unmask("DjfkCZLtcBLn"), "288230376151711717");
equal(keymask.unmask("YcWfgzxKYXFW"), "18446744073709551556");
});

it("should process binary data", () => {
const buffer1 = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]).buffer;
const buffer2 = new Uint8Array([11, 22, 33, 44, 55, 66, 77, 88, 99]).buffer;
equal(keymask.mask(buffer1), "NpRcJcFtscDkjdfXLfFWGtqR");
equal(keymask.mask(buffer2), "HXmKjxGXGXBKTD");
deepEqual(keymask.unmask("NpRcJcFtscDkjdfXLfFWGtqR"), "21345817372864405881847059188222722561");
deepEqual(keymask.unmask("HXmKjxGXGXBKTD"), "1832590477950520989195");
});
});

describe("ArrayBuffer output", () => {
const keymask = new Keymask({type: "buffer"});

Expand Down

0 comments on commit 29bb2be

Please sign in to comment.