Refactor code structure for improved readability and maintainability

This commit is contained in:
2025-12-27 05:09:15 +00:00
parent 2d80d53b70
commit 8b95824cb6
7 changed files with 414 additions and 465 deletions
+2 -5
View File
@@ -18,12 +18,9 @@
"test": "vitest run --config vitest.config.ts"
},
"dependencies": {
"js-sha512": "^0.8.0"
"js-sha512": "^0.9.0"
},
"devDependencies": {
"chai": "^4.3.10",
"chai-bytes": "^0.1.2",
"lodash-es": "^4.17.21",
"vitest": "^2.0.5"
"vitest": "^4.0.16"
}
}
+1 -1
View File
@@ -1,2 +1,2 @@
export type Bytes = ArrayBuffer | DataView | Uint8Array;
export type Bytes = ArrayBuffer | DataView | Uint8Array<ArrayBuffer>;
export type BytesLike = Bytes | string;
+2 -2
View File
@@ -121,7 +121,7 @@ export class LiteHashDRBG implements RNG {
return maxInt;
}
public nextBytes(bytes: number, baseBytes?: number): Uint8Array {
public nextBytes(bytes: number, baseBytes?: number): Uint8Array<ArrayBuffer> {
bytes |= 0;
if (bytes <= 0) {
throw new Error(`${bytes} <= 0`);
@@ -171,7 +171,7 @@ export class LiteHashDRBG implements RNG {
return result;
}
public nextBits(bits: number, baseBytes?: number): Uint8Array {
public nextBits(bits: number, baseBytes?: number): Uint8Array<ArrayBuffer> {
bits |= 0;
const bytes = (bits + 7) >> 3;
const headBits = bits & 0x7;
+2 -2
View File
@@ -5,8 +5,8 @@ export interface RNG {
*/
getMaxInt(): number;
nextBytes(bytes: number): Uint8Array;
nextBits(bits: number): Uint8Array;
nextBytes(bytes: number): Uint8Array<ArrayBuffer>;
nextBits(bits: number): Uint8Array<ArrayBuffer>;
nextInt(max?: number): number;
nextFloat1(): number;
@@ -1,17 +1,31 @@
import type { BytesLike } from './BytesLike.js';
export function convertBytesLikeToUint8Array(data: BytesLike, encodeUTF8 = true): Uint8Array {
export function convertBytesLikeToUint8Array(
data: BytesLike,
encodeUTF8 = true
): Uint8Array<ArrayBuffer> {
if (data instanceof Uint8Array) {
return data;
if (
data.buffer instanceof ArrayBuffer
&& data.byteOffset === 0
&& data.byteLength === data.buffer.byteLength
) {
return data;
}
return new Uint8Array(data) as Uint8Array<ArrayBuffer>;
}
if (data instanceof ArrayBuffer) {
return new Uint8Array(data);
}
if (data instanceof DataView) {
const view = new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
return new Uint8Array(view) as Uint8Array<ArrayBuffer>;
}
if (typeof (data) === 'string') {
if (encodeUTF8) {
return (new TextEncoder()).encode(data);
return (new TextEncoder()).encode(data) as Uint8Array<ArrayBuffer>;
}
return new Uint8Array(data.split('').map((s) => s.codePointAt(0) as number));
return new Uint8Array(data.split('').map((s) => s.codePointAt(0) as number)) as Uint8Array<ArrayBuffer>;
}
return new Uint8Array(data.buffer);
throw new Error('Unsupported BytesLike');
}
+75 -72
View File
@@ -1,17 +1,23 @@
import chai, { assert } from 'chai';
import chaiBytes from 'chai-bytes';
import { describe, expect, it } from 'vitest';
import { bufferByteSize, LiteHashDRBG } from '../src/util/LiteHashDRBG.js';
import { RandUtil } from '../src/util/RandUtil.js';
import { convertBytesLikeToArrayBuffer } from '../src/util/convertBytesLikeToArrayBuffer.js';
import { convertBytesLikeToUint8Array as toBytes } from '../src/util/convertBytesLikeToUint8Array.js';
import _ from 'lodash-es';
chai.use(chaiBytes);
type Bytes = ArrayBuffer | DataView | Uint8Array;
type Bytes = ArrayBuffer | DataView | Uint8Array<ArrayBuffer>;
type MaybeBytes = Bytes | string;
function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferByteSize): Uint8Array {
const range = (count: number): number[] => Array.from({ length: count }, (_, idx) => idx);
const expectBytes = (actual: Uint8Array, expected: Uint8Array): void => {
expect(Array.from(actual)).toEqual(Array.from(expected));
};
function fillBlock(
body: MaybeBytes,
filler: MaybeBytes = '\0',
length = bufferByteSize
): Uint8Array<ArrayBuffer> {
const u8Body = toBytes(body);
const u8Filler = toBytes(filler, false);
@@ -19,7 +25,7 @@ function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferB
throw new Error('filler must have length');
}
const buffer = new Uint8Array(length);
const buffer = new Uint8Array<ArrayBuffer>(length);
buffer.set(u8Body, 0);
let bufferIdx = u8Body.byteLength;
@@ -36,7 +42,6 @@ function fillBlock(body: MaybeBytes, filler: MaybeBytes = '\0', length = bufferB
return buffer;
}
class DummyBlockRNG extends LiteHashDRBG {
private repeatBlockCnt: number;
private repeatBlock: ArrayBuffer[];
@@ -48,7 +53,7 @@ class DummyBlockRNG extends LiteHashDRBG {
for (const rawBlock of repeatBlock) {
const block = convertBytesLikeToArrayBuffer(rawBlock);
if (block.byteLength !== bufferByteSize) {
throw new Error;
throw new Error('invalid block size');
}
this.repeatBlock.push(block);
}
@@ -70,25 +75,26 @@ class DummyBlockRNG extends LiteHashDRBG {
const fixedKey = 'HelloWorld';
describe('RNGtestDummy', () => {
const rng = new DummyBlockRNG([
fillBlock('', "\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff")
]);
it('BasicConvert', () => {
assert.equal(toBytes("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", false).length, 16);
expect(toBytes("\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff", false).length)
.toBe(16);
});
it('SimpleByte', () => {
assert.equalBytes(toBytes("\x00", false), rng.nextBytes(1), 'b1');
assert.equalBytes(toBytes("\x11\x22", false), rng.nextBytes(2), 'b2');
assert.equalBytes(toBytes("\x33\x44\x55", false), rng.nextBytes(3), 'b3');
assert.equalBytes(toBytes("\x66\x77\x88\x99", false), rng.nextBytes(4), 'b4');
expectBytes(toBytes("\x00", false), rng.nextBytes(1));
expectBytes(toBytes("\x11\x22", false), rng.nextBytes(2));
expectBytes(toBytes("\x33\x44\x55", false), rng.nextBytes(3));
expectBytes(toBytes("\x66\x77\x88\x99", false), rng.nextBytes(4));
});
it('OverflowBlock', () => {
for (const idx in _.range(16)) {
assert.equalBytes(
for (let idx = 0; idx < 16; idx += 1) {
expectBytes(
toBytes("\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", false),
rng.nextBytes(16)
);
@@ -96,41 +102,41 @@ describe('RNGtestDummy', () => {
});
it('MultiBlock', () => {
assert.equalBytes(
expectBytes(
fillBlock('', "\xaa\xbb\xcc\xdd\xee\xff\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99", bufferByteSize * 2),
rng.nextBytes(bufferByteSize * 2)
);
});
it('bitTest', () => {
assert.equalBytes(toBytes("\x00", false), rng.nextBits(1)); //aa
assert.equalBytes(toBytes("\x01", false), rng.nextBits(1)); //bb
assert.equalBytes(toBytes("\xcc", false), rng.nextBits(8)); //cc
assert.equalBytes(toBytes("\xdd\x02", false), rng.nextBits(10)); //ddee
assert.equalBytes(toBytes("\x7f", false), rng.nextBits(7)); //ff
assert.equalBytes(toBytes("\x00\x11\x22\x33\x44\x55\x06", false), rng.nextBits(53));
expectBytes(toBytes("\x00", false), rng.nextBits(1)); //aa
expectBytes(toBytes("\x01", false), rng.nextBits(1)); //bb
expectBytes(toBytes("\xcc", false), rng.nextBits(8)); //cc
expectBytes(toBytes("\xdd\x02", false), rng.nextBits(10)); //ddee
expectBytes(toBytes("\x7f", false), rng.nextBits(7)); //ff
expectBytes(toBytes("\x00\x11\x22\x33\x44\x55\x06", false), rng.nextBits(53));
});
it('int', () => {
assert.equal(0x77, rng.nextInt(0xff));
assert.equal(0x9988, rng.nextInt((1 << 16) - 1));
assert.equal(0xddccbbaa, rng.nextInt(0xffffffff));
assert.equal(0x0433221100ffee, rng.nextInt());
assert.equal(0x05, rng.nextInt(0x0f)); //55
assert.equal(0x06, rng.nextInt(0x12)); //66
assert.equal(0x08, rng.nextInt(99)); //77(119 -> 7bit) -> 88(136 -> 8bit -> 8)
assert.equal(0x99, rng.nextInt(0x99)); //99
assert.equal(0xaa, rng.nextInt(0xaa)); //aa (fit Max)
expect(rng.nextInt(0xff)).toBe(0x77);
expect(rng.nextInt((1 << 16) - 1)).toBe(0x9988);
expect(rng.nextInt(0xffffffff)).toBe(0xddccbbaa);
expect(rng.nextInt()).toBe(0x0433221100ffee);
expect(rng.nextInt(0x0f)).toBe(0x05); //55
expect(rng.nextInt(0x12)).toBe(0x06); //66
expect(rng.nextInt(99)).toBe(0x08); //77(119 -> 7bit) -> 88(136 -> 8bit -> 8)
expect(rng.nextInt(0x99)).toBe(0x99); //99
expect(rng.nextInt(0xaa)).toBe(0xaa); //aa (fit Max)
});
it('float', () => {
const floatMax = 2 ** 53;
const fa = rng.nextFloat1();
assert.equal(0x1100ffeeddccbb / floatMax, fa);
assert.isTrue(0.5313720384 > fa);
assert.isTrue(0.5313720383 < fa);
expect(fa).toBe(0x1100ffeeddccbb / floatMax);
expect(0.5313720384 > fa).toBe(true);
expect(0.5313720383 < fa).toBe(true);
const fb = rng.nextFloat1();
assert.equal(0x08776655443322 / floatMax, fb);
expect(fb).toBe(0x08776655443322 / floatMax);
});
});
@@ -147,8 +153,7 @@ describe('RandUtilDummy', () => {
* 2, [7,0,1,2,3,4,6,5]
* 1, [7,0,1,2,3,4,5,6]
*/
assert.deepEqual(
randUtil.shuffle(Array.from(_.range(8))),
expect(randUtil.shuffle(range(8))).toEqual(
[7, 0, 1, 2, 3, 4, 5, 6]
);
@@ -163,8 +168,7 @@ describe('RandUtilDummy', () => {
* 1, [0,8,1,2,3,4,5,6,7,9]
* 0, [0,8,1,2,3,4,5,6,7,9]
*/
assert.deepEqual(
randUtil.shuffle(Array.from(_.range(10))),
expect(randUtil.shuffle(range(10))).toEqual(
[0, 8, 1, 2, 3, 4, 5, 6, 7, 9]
);
});
@@ -172,22 +176,21 @@ describe('RandUtilDummy', () => {
const rng = new DummyBlockRNG([fillBlock('', '\x17\x16\x15\x14\x13\x12\x11\x10')]);
const randUtil = new RandUtil(rng);
it('choice', () => {
//0x17(7), 0x16(6)
assert.equal(randUtil.choice([0, 1, 2, 3, 4, 5]), 5);
expect(randUtil.choice([0, 1, 2, 3, 4, 5])).toBe(5);
//0x15(5), Set 순서 유지
assert.equal(randUtil.choice(new Set([5, 3, 1, 2, 8, 0])), 8);
expect(randUtil.choice(new Set([5, 3, 1, 2, 8, 0]))).toBe(8);
//0x14(4), 정렬 순서상 숫자(소-대) > 문자열(삽입순) > 심볼 순서
assert.equal(randUtil.choice({ c: 'c', a: 'a', b: 'b', 4: 'x', 2: 't', '3': 'q' }), 'c');
expect(randUtil.choice({ c: 'c', a: 'a', b: 'b', 4: 'x', 2: 't', '3': 'q' }))
.toBe('c');
});
it('choiceUsingWeight', () => {
//0.6275740099377194 * 38.1 = 23.91
assert.equal(randUtil.choiceUsingWeight({
expect(randUtil.choiceUsingWeight({
a: 0.1,
b: 10,
tt: 2,
@@ -195,50 +198,50 @@ describe('RandUtilDummy', () => {
c: 20,
d: 0,
e: 6
}), 'c');
})).toBe('c');
//0.658946544056166
assert.equal(randUtil.choiceUsingWeightPair([
expect(randUtil.choiceUsingWeightPair([
['xx', 10],
]), 'xx');
])).toBe('xx');
//0.6903152783785083 * 27.3 = 18.84560709973328
assert.equal(randUtil.choiceUsingWeightPair([
expect(randUtil.choiceUsingWeightPair([
['e', 10],
['d', 4],
['c', 0.1],
['baba', 0.2],
['q', 9],
['xt', 4]
]), 'q');
])).toBe('q');
});
});
describe('RNGexpectedError', () => {
const rng = new LiteHashDRBG(fixedKey);
it('nextBits0', () => {
assert.throw(() => rng.nextBits(0));
expect(() => rng.nextBits(0)).toThrow();
});
it('nextBits-1', () => {
assert.throw(() => rng.nextBits(-1));
expect(() => rng.nextBits(-1)).toThrow();
});
it('nextBytes0', () => {
assert.throw(() => rng.nextBytes(0));
expect(() => rng.nextBytes(0)).toThrow();
});
it('nextBytes-1', () => {
assert.throw(() => rng.nextBytes(-1));
expect(() => rng.nextBytes(-1)).toThrow();
});
const randUtil = new RandUtil(rng);
it('utilEmptyChoice', () => {
assert.throw(() => randUtil.choice([]));
expect(() => randUtil.choice([])).toThrow();
});
it('utilEmptyChoiceUsingWeight', () => {
assert.throw(() => randUtil.choiceUsingWeight({}));
expect(() => randUtil.choiceUsingWeight({})).toThrow();
});
it('utilEmptyChoiceUsingWeightPair', () => {
assert.throw(() => randUtil.choiceUsingWeightPair([]));
expect(() => randUtil.choiceUsingWeightPair([])).toThrow();
});
});
@@ -275,11 +278,11 @@ describe('RNGAcceptable', () => {
it('RNGLong', () => {
const longKey = fixedKey;
for (const _a of _.range(8)) {
for (let idx = 0; idx < 8; idx += 1) {
longKey.concat(longKey);
}
const rngLong = new LiteHashDRBG(longKey);
for (const _a of _.range(10)) {
for (let idx = 0; idx < 10; idx += 1) {
rngLong.nextBytes(16);
}
});
@@ -314,21 +317,21 @@ describe('RNG', () => {
const rng = new LiteHashDRBG(fixedKey);
let offset = 0;
assert.equalBytes(rng.nextBytes(10), testVector.slice(offset, offset + 10), '1');
expectBytes(rng.nextBytes(10), testVector.slice(offset, offset + 10));
offset += 10;
assert.equalBytes(rng.nextBytes(32), testVector.slice(offset, offset + 32), '2');
expectBytes(rng.nextBytes(32), testVector.slice(offset, offset + 32));
offset += 32;
assert.equalBytes(rng.nextBytes(1), testVector.slice(offset, offset + 1), '3');
expectBytes(rng.nextBytes(1), testVector.slice(offset, offset + 1));
offset += 1;
assert.equalBytes(rng.nextBytes(64), testVector.slice(offset, offset + 64), '4');
expectBytes(rng.nextBytes(64), testVector.slice(offset, offset + 64));
offset += 64;
assert.equalBytes(rng.nextBytes(5), testVector.slice(offset, offset + 5), '5');
expectBytes(rng.nextBytes(5), testVector.slice(offset, offset + 5));
offset += 5;
const lastA = rng.nextBytes(16, 18);
const lastB = new Uint8Array(18);
lastB.set(testVector.slice(offset, offset + 16));
assert.equalBytes(lastA, lastB);
expectBytes(lastA, lastB);
});
it('bits', () => {
@@ -344,11 +347,11 @@ describe('RNG', () => {
const B = new Uint8Array(testVector.slice(offset, offset + bytes));
offset += bytes;
if (bits % 8 != 0) {
if (bits % 8 !== 0) {
const bitMask = 0xff >> (8 - (bits % 8));
B[bytes - 1] &= bitMask;
}
assert.equalBytes(A, B);
expectBytes(A, B);
}
});
@@ -362,8 +365,8 @@ describe('RNG', () => {
new Uint8Array(testVector.slice(bufferByteSize * 4, bufferByteSize * 5)),
]);
for (const idx of _.range(18)) {
assert.equal(rng.nextFloat1(), rng2.nextFloat1(), `float${idx}`);
for (const idx of range(18)) {
expect(rng.nextFloat1()).toBe(rng2.nextFloat1());
}
});