@ddunigma/node 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/encoders/Ddu64.d.ts +4 -0
- package/dist/cjs/encoders/Ddu64.js +198 -153
- package/dist/mjs/encoders/Ddu64.d.ts +4 -0
- package/dist/mjs/encoders/Ddu64.js +196 -149
- package/package.json +1 -1
|
@@ -15,6 +15,10 @@ export declare class Ddu64 extends BaseDdu {
|
|
|
15
15
|
private readonly indexToBinaryCache;
|
|
16
16
|
constructor(dduChar?: string[] | string, paddingChar?: string, dduOptions?: DduConstructorOptions);
|
|
17
17
|
private normalizeCharSet;
|
|
18
|
+
private resolveInitialCharSet;
|
|
19
|
+
private getFallbackCharSet;
|
|
20
|
+
private shouldUsePowerOfTwo;
|
|
21
|
+
private getCharSetOrThrow;
|
|
18
22
|
private getBinaryFromIndex;
|
|
19
23
|
private validateCombinationDuplicates;
|
|
20
24
|
encode(input: Buffer | string, _options?: DduOptions): string;
|
|
@@ -6,86 +6,18 @@ const types_1 = require("../types");
|
|
|
6
6
|
const charSets_1 = require("../charSets");
|
|
7
7
|
class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
8
8
|
constructor(dduChar, paddingChar, dduOptions) {
|
|
9
|
-
var _a, _b
|
|
9
|
+
var _a, _b;
|
|
10
10
|
super();
|
|
11
11
|
this.dduBinaryLookup = new Map();
|
|
12
12
|
const shouldThrowError = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.useBuildErrorReturn) !== null && _a !== void 0 ? _a : false;
|
|
13
|
-
const
|
|
14
|
-
const cs = (0, charSets_1.getCharSet)(symbol);
|
|
15
|
-
if (!cs)
|
|
16
|
-
throw new Error(`CharSet with symbol ${symbol} not found`);
|
|
17
|
-
return cs;
|
|
18
|
-
};
|
|
19
|
-
// charset 초기화
|
|
20
|
-
let charSet, padding, requiredLength, bitLength, isPredefined;
|
|
21
|
-
try {
|
|
22
|
-
const finalDduChar = dduChar !== null && dduChar !== void 0 ? dduChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduChar;
|
|
23
|
-
const finalPadding = paddingChar !== null && paddingChar !== void 0 ? paddingChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.paddingChar;
|
|
24
|
-
if (finalDduChar) {
|
|
25
|
-
// 커스텀 charset
|
|
26
|
-
if (!finalPadding) {
|
|
27
|
-
throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
|
|
28
|
-
}
|
|
29
|
-
// 문자열을 배열로 변환 (중복 제거 없이)
|
|
30
|
-
const arr = typeof finalDduChar === "string" ? [...finalDduChar.trim()] : finalDduChar;
|
|
31
|
-
// 중복 검사 (useBuildErrorReturn이 true일 때만)
|
|
32
|
-
if (shouldThrowError) {
|
|
33
|
-
const uniqueChars = new Set(arr);
|
|
34
|
-
if (uniqueChars.size !== arr.length) {
|
|
35
|
-
const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
|
|
36
|
-
throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const len = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.requiredLength) !== null && _b !== void 0 ? _b : arr.length;
|
|
40
|
-
if (arr.length < len) {
|
|
41
|
-
throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
|
|
42
|
-
}
|
|
43
|
-
const usePow2 = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true || ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && len > 0 && (len & (len - 1)) === 0);
|
|
44
|
-
if (usePow2) {
|
|
45
|
-
const exp = this.getLargestPowerOfTwoExponent(len);
|
|
46
|
-
const pow2Len = 1 << exp;
|
|
47
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, pow2Len), finalPadding, pow2Len, exp, false];
|
|
48
|
-
}
|
|
49
|
-
else {
|
|
50
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, len), finalPadding, len, this.getBitLength(len), false];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
else if (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) {
|
|
54
|
-
// 미리 정의된 charset
|
|
55
|
-
const cs = getCharSetFromSymbol(dduOptions.dduSetSymbol);
|
|
56
|
-
const usePow2 = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true || ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && cs.maxRequiredLength > 0 && (cs.maxRequiredLength & (cs.maxRequiredLength - 1)) === 0);
|
|
57
|
-
if (usePow2) {
|
|
58
|
-
const exp = this.getLargestPowerOfTwoExponent(cs.maxRequiredLength);
|
|
59
|
-
const pow2Len = 1 << exp;
|
|
60
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet.slice(0, pow2Len), cs.paddingChar, pow2Len, exp, true];
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
// 기본 charset
|
|
68
|
-
const cs = getCharSetFromSymbol((_c = types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _c !== void 0 ? _c : types_1.DduSetSymbol.DDU);
|
|
69
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
catch (error) {
|
|
73
|
-
if (shouldThrowError)
|
|
74
|
-
throw error;
|
|
75
|
-
// fallback
|
|
76
|
-
const fallbackSymbol = (_e = (_d = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) !== null && _d !== void 0 ? _d : types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _e !== void 0 ? _e : types_1.DduSetSymbol.ONECHARSET;
|
|
77
|
-
const cs = (_f = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _f !== void 0 ? _f : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
|
|
78
|
-
if (!cs)
|
|
79
|
-
throw new Error(`Critical: No fallback CharSet available`);
|
|
80
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
81
|
-
}
|
|
13
|
+
const { charSet, padding, requiredLength, bitLength, isPredefined, } = this.resolveInitialCharSet(dduChar, paddingChar, dduOptions, shouldThrowError);
|
|
82
14
|
// 정규화 및 검증
|
|
83
15
|
const normalized = this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
|
|
84
16
|
this.dduChar = normalized.charSet;
|
|
85
17
|
this.paddingChar = normalized.padding;
|
|
86
18
|
this.charLength = normalized.charLength;
|
|
87
19
|
this.isPredefinedCharSet = normalized.isPredefined;
|
|
88
|
-
this.encoding = (
|
|
20
|
+
this.encoding = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _b !== void 0 ? _b : this.defaultEncoding;
|
|
89
21
|
const dduLength = this.dduChar.length;
|
|
90
22
|
const recalculatedBitLength = this.getBitLength(dduLength);
|
|
91
23
|
this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
|
|
@@ -96,7 +28,13 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
96
28
|
this.maxBinaryValue = Math.pow(2, this.effectiveBitLength);
|
|
97
29
|
// 성능 최적화: 단일 구현 사용 (벤치마크 결과 Method2가 더 빠르고 안정적)
|
|
98
30
|
// Method2는 32비트 이상에서도 오버플로우 없이 정확한 결과 제공
|
|
99
|
-
this.binaryChunkToIntFn = (chunk) =>
|
|
31
|
+
this.binaryChunkToIntFn = (chunk) => {
|
|
32
|
+
let value = 0;
|
|
33
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
34
|
+
value = value * 2 + (chunk.charCodeAt(i) & 1);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
};
|
|
100
38
|
this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
|
|
101
39
|
if (this.charLength === 1 && !this.isPredefinedCharSet) {
|
|
102
40
|
this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
|
|
@@ -109,70 +47,180 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
109
47
|
}
|
|
110
48
|
}
|
|
111
49
|
normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (!cs)
|
|
118
|
-
throw new Error(`Critical: No fallback CharSet available`);
|
|
119
|
-
return { charSet: cs.charSet, padding: cs.paddingChar, requiredLength: cs.maxRequiredLength, bitLength: cs.bitLength, isPredefined: true };
|
|
120
|
-
};
|
|
121
|
-
const validate = (condition, message) => {
|
|
122
|
-
if (condition) {
|
|
50
|
+
const applyFallback = () => this.getFallbackCharSet(dduOptions);
|
|
51
|
+
while (true) {
|
|
52
|
+
const ensure = (condition, message) => {
|
|
53
|
+
if (!condition)
|
|
54
|
+
return false;
|
|
123
55
|
if (shouldThrowError)
|
|
124
56
|
throw new Error(message);
|
|
125
|
-
|
|
126
|
-
|
|
57
|
+
({
|
|
58
|
+
charSet,
|
|
59
|
+
padding,
|
|
60
|
+
requiredLength,
|
|
61
|
+
bitLength,
|
|
62
|
+
isPredefined,
|
|
63
|
+
} = applyFallback());
|
|
127
64
|
return true;
|
|
65
|
+
};
|
|
66
|
+
const uniqueChars = new Set(charSet);
|
|
67
|
+
if (uniqueChars.size !== charSet.length) {
|
|
68
|
+
const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
|
|
69
|
+
const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[
|
|
70
|
+
...new Set(duplicates),
|
|
71
|
+
].join(", ")}]`;
|
|
72
|
+
if (shouldThrowError)
|
|
73
|
+
throw new Error(errorMsg);
|
|
74
|
+
charSet = Array.from(uniqueChars);
|
|
75
|
+
if (!isPredefined) {
|
|
76
|
+
requiredLength = charSet.length;
|
|
77
|
+
}
|
|
128
78
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// 중복 제거 (모든 charset에 적용)
|
|
132
|
-
const uniqueChars = new Set(charSet);
|
|
133
|
-
if (uniqueChars.size !== charSet.length) {
|
|
134
|
-
const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
|
|
135
|
-
const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`;
|
|
136
|
-
if (shouldThrowError)
|
|
137
|
-
throw new Error(errorMsg);
|
|
138
|
-
// 중복 제거 후 계속 진행
|
|
139
|
-
charSet = Array.from(uniqueChars);
|
|
140
|
-
if (!isPredefined) {
|
|
141
|
-
requiredLength = charSet.length;
|
|
79
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters. Required: ${requiredLength}, Provided: ${charSet.length}`)) {
|
|
80
|
+
continue;
|
|
142
81
|
}
|
|
82
|
+
if (ensure(requiredLength < 2, `[Ddu64 normalizeCharSet] At least 2 unique characters are required. Provided: ${requiredLength}`)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (ensure(bitLength <= 0, `[Ddu64 normalizeCharSet] Invalid bit length (${bitLength}) for charset size ${requiredLength}`)) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset. Required: ${requiredLength} characters`)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
let charLength = charSet[0].length;
|
|
92
|
+
const invalidIndex = charSet.findIndex((char) => char.length !== charLength);
|
|
93
|
+
if (invalidIndex !== -1) {
|
|
94
|
+
if (shouldThrowError) {
|
|
95
|
+
throw new Error(`[Ddu64 normalizeCharSet] Inconsistent character length. Expected: ${charLength}, but character at index ${invalidIndex} ("${charSet[invalidIndex]}") has length ${charSet[invalidIndex].length}`);
|
|
96
|
+
}
|
|
97
|
+
charSet = charSet.filter((char) => char.length === charLength);
|
|
98
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after filtering. Required: ${requiredLength}, Remaining: ${charSet.length}`)) {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after filtering`)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
charLength = charSet[0].length;
|
|
105
|
+
}
|
|
106
|
+
if (ensure(padding.length !== charLength, `[Ddu64 normalizeCharSet] Padding character length mismatch. Expected: ${charLength}, Got: ${padding.length} (padding: "${padding}")`)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (charSet.includes(padding)) {
|
|
110
|
+
if (shouldThrowError) {
|
|
111
|
+
throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
|
|
112
|
+
}
|
|
113
|
+
charSet = charSet.filter((char) => char !== padding);
|
|
114
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after removing padding conflict`)) {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
charLength = charSet[0].length;
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
charSet: charSet.slice(0, requiredLength),
|
|
124
|
+
padding,
|
|
125
|
+
requiredLength,
|
|
126
|
+
bitLength,
|
|
127
|
+
isPredefined,
|
|
128
|
+
charLength,
|
|
129
|
+
};
|
|
143
130
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
131
|
+
}
|
|
132
|
+
resolveInitialCharSet(dduChar, paddingChar, dduOptions, shouldThrowError) {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
const finalize = (set, padding, length, baseBitLength, predefined = false) => {
|
|
135
|
+
const usePow2 = this.shouldUsePowerOfTwo(length, dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo);
|
|
136
|
+
if (usePow2 && length > 0) {
|
|
137
|
+
const exponent = this.getLargestPowerOfTwoExponent(length);
|
|
138
|
+
const pow2Length = 1 << exponent;
|
|
139
|
+
return {
|
|
140
|
+
charSet: set.slice(0, pow2Length),
|
|
141
|
+
padding,
|
|
142
|
+
requiredLength: pow2Length,
|
|
143
|
+
bitLength: exponent,
|
|
144
|
+
isPredefined: predefined,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const computedBitLength = baseBitLength !== null && baseBitLength !== void 0 ? baseBitLength : (length > 0 ? this.getBitLength(length) : 0);
|
|
148
|
+
return {
|
|
149
|
+
charSet: set.slice(0, length),
|
|
150
|
+
padding,
|
|
151
|
+
requiredLength: length,
|
|
152
|
+
bitLength: computedBitLength,
|
|
153
|
+
isPredefined: predefined,
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
const fallback = () => this.getFallbackCharSet(dduOptions);
|
|
157
|
+
try {
|
|
158
|
+
const finalDduChar = dduChar !== null && dduChar !== void 0 ? dduChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduChar;
|
|
159
|
+
const finalPadding = paddingChar !== null && paddingChar !== void 0 ? paddingChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.paddingChar;
|
|
160
|
+
if (finalDduChar) {
|
|
161
|
+
if (!finalPadding) {
|
|
162
|
+
throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
|
|
163
|
+
}
|
|
164
|
+
const arr = typeof finalDduChar === "string"
|
|
165
|
+
? [...finalDduChar.trim()]
|
|
166
|
+
: [...finalDduChar];
|
|
167
|
+
if (shouldThrowError) {
|
|
168
|
+
const uniqueChars = new Set(arr);
|
|
169
|
+
if (uniqueChars.size !== arr.length) {
|
|
170
|
+
const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
|
|
171
|
+
throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[
|
|
172
|
+
...new Set(duplicates),
|
|
173
|
+
].join(", ")}]`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const len = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.requiredLength) !== null && _a !== void 0 ? _a : arr.length;
|
|
177
|
+
if (arr.length < len) {
|
|
178
|
+
throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
|
|
179
|
+
}
|
|
180
|
+
return finalize(arr, finalPadding, len, undefined, false);
|
|
181
|
+
}
|
|
182
|
+
if (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) {
|
|
183
|
+
const cs = this.getCharSetOrThrow(dduOptions.dduSetSymbol);
|
|
184
|
+
return finalize(cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true);
|
|
185
|
+
}
|
|
186
|
+
const defaultSymbol = (_b = types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _b !== void 0 ? _b : types_1.DduSetSymbol.DDU;
|
|
187
|
+
const cs = this.getCharSetOrThrow(defaultSymbol);
|
|
188
|
+
return finalize(cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true);
|
|
150
189
|
}
|
|
151
|
-
|
|
152
|
-
|
|
190
|
+
catch (error) {
|
|
191
|
+
if (shouldThrowError)
|
|
192
|
+
throw error;
|
|
193
|
+
return fallback();
|
|
153
194
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
195
|
+
}
|
|
196
|
+
getFallbackCharSet(dduOptions) {
|
|
197
|
+
var _a, _b, _c;
|
|
198
|
+
const fallbackSymbol = (_b = (_a = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) !== null && _a !== void 0 ? _a : types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _b !== void 0 ? _b : types_1.DduSetSymbol.ONECHARSET;
|
|
199
|
+
const cs = (_c = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _c !== void 0 ? _c : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
|
|
200
|
+
if (!cs)
|
|
201
|
+
throw new Error(`Critical: No fallback CharSet available`);
|
|
202
|
+
return {
|
|
203
|
+
charSet: cs.charSet,
|
|
204
|
+
padding: cs.paddingChar,
|
|
205
|
+
requiredLength: cs.maxRequiredLength,
|
|
206
|
+
bitLength: cs.bitLength,
|
|
207
|
+
isPredefined: true,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
shouldUsePowerOfTwo(length, preference) {
|
|
211
|
+
if (preference === true) {
|
|
212
|
+
return length > 0;
|
|
164
213
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (new Set(charSet).has(padding)) {
|
|
168
|
-
if (shouldThrowError) {
|
|
169
|
-
throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
|
|
170
|
-
}
|
|
171
|
-
charSet = charSet.filter(char => char !== padding);
|
|
172
|
-
validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`);
|
|
173
|
-
charLength = (_f = (_e = charSet[0]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0;
|
|
214
|
+
if (preference === false) {
|
|
215
|
+
return false;
|
|
174
216
|
}
|
|
175
|
-
return
|
|
217
|
+
return length > 0 && (length & (length - 1)) === 0;
|
|
218
|
+
}
|
|
219
|
+
getCharSetOrThrow(symbol) {
|
|
220
|
+
const cs = (0, charSets_1.getCharSet)(symbol);
|
|
221
|
+
if (!cs)
|
|
222
|
+
throw new Error(`CharSet with symbol ${symbol} not found`);
|
|
223
|
+
return cs;
|
|
176
224
|
}
|
|
177
225
|
getBinaryFromIndex(index) {
|
|
178
226
|
var _a, _b;
|
|
@@ -213,27 +261,23 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
213
261
|
encode(input, _options) {
|
|
214
262
|
// options는 구버전 호환성을 위해 유지하지만 사용하지 않음
|
|
215
263
|
const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
|
|
216
|
-
|
|
217
|
-
const dduLength = this.dduChar.length;
|
|
218
|
-
const effectiveBitLength = this.effectiveBitLength;
|
|
219
|
-
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
|
|
220
|
-
// 문자열 연결 최적화: Array + join 사용
|
|
264
|
+
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, this.effectiveBitLength);
|
|
221
265
|
const resultParts = new Array(dduBinary.length);
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const charInt = this.binaryChunkToIntFn(binaryChunk);
|
|
226
|
-
if (!this.usePowerOfTwo) {
|
|
227
|
-
// 가변 길이 조합 인코딩 (멀티바이트 문자도 지원)
|
|
228
|
-
const quotient = Math.floor(charInt / dduLength);
|
|
229
|
-
const remainder = charInt % dduLength;
|
|
230
|
-
resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
|
|
231
|
-
}
|
|
232
|
-
else {
|
|
233
|
-
// 고정 길이 직접 매핑
|
|
266
|
+
if (this.usePowerOfTwo) {
|
|
267
|
+
for (let i = 0; i < dduBinary.length; i++) {
|
|
268
|
+
const charInt = this.binaryChunkToIntFn(dduBinary[i]);
|
|
234
269
|
resultParts[i] = this.dduChar[charInt];
|
|
235
270
|
}
|
|
236
271
|
}
|
|
272
|
+
else {
|
|
273
|
+
const dduLength = this.dduChar.length;
|
|
274
|
+
for (let i = 0; i < dduBinary.length; i++) {
|
|
275
|
+
const value = this.binaryChunkToIntFn(dduBinary[i]);
|
|
276
|
+
const quotient = Math.floor(value / dduLength);
|
|
277
|
+
const remainder = value % dduLength;
|
|
278
|
+
resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
237
281
|
let resultString = resultParts.join("");
|
|
238
282
|
// 패딩 비트 정보를 padChar + 패딩비트수 형태로 추가
|
|
239
283
|
if (padding > 0) {
|
|
@@ -262,13 +306,14 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
262
306
|
input = input.substring(0, padCharIndex);
|
|
263
307
|
}
|
|
264
308
|
}
|
|
265
|
-
|
|
309
|
+
const binaryParts = [];
|
|
310
|
+
const charLength = this.charLength;
|
|
266
311
|
const dduLength = this.dduChar.length;
|
|
267
312
|
if (!this.usePowerOfTwo) {
|
|
268
|
-
const chunkSize =
|
|
313
|
+
const chunkSize = charLength * 2;
|
|
269
314
|
for (let i = 0; i < input.length; i += chunkSize) {
|
|
270
|
-
const firstChar = input.slice(i, i +
|
|
271
|
-
const secondChar = input.slice(i +
|
|
315
|
+
const firstChar = input.slice(i, i + charLength);
|
|
316
|
+
const secondChar = input.slice(i + charLength, i + chunkSize);
|
|
272
317
|
const firstIndex = this.dduBinaryLookup.get(firstChar);
|
|
273
318
|
const secondIndex = this.dduBinaryLookup.get(secondChar);
|
|
274
319
|
if (firstIndex === undefined || secondIndex === undefined) {
|
|
@@ -279,23 +324,23 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
279
324
|
if (value >= this.maxBinaryValue) {
|
|
280
325
|
throw new Error(`[Ddu64 decode] Invalid character combination detected. Calculated value ${value} exceeds binary range ${this.maxBinaryValue - 1}.`);
|
|
281
326
|
}
|
|
282
|
-
|
|
327
|
+
binaryParts.push(this.getBinaryFromIndex(value));
|
|
283
328
|
}
|
|
284
329
|
}
|
|
285
330
|
else {
|
|
286
|
-
for (let i = 0; i < input.length; i +=
|
|
287
|
-
const charChunk = input.slice(i, i +
|
|
331
|
+
for (let i = 0; i < input.length; i += charLength) {
|
|
332
|
+
const charChunk = input.slice(i, i + charLength);
|
|
288
333
|
const charIndex = this.dduBinaryLookup.get(charChunk);
|
|
289
334
|
if (charIndex === undefined) {
|
|
290
|
-
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${
|
|
335
|
+
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${charLength}`);
|
|
291
336
|
}
|
|
292
337
|
if (charIndex >= this.maxBinaryValue) {
|
|
293
338
|
throw new Error(`[Ddu64 decode] Invalid binary index ${charIndex}. Allowed range: 0-${this.maxBinaryValue - 1}`);
|
|
294
339
|
}
|
|
295
|
-
|
|
340
|
+
binaryParts.push(this.getBinaryFromIndex(charIndex));
|
|
296
341
|
}
|
|
297
342
|
}
|
|
298
|
-
return this.dduBinaryToBuffer(
|
|
343
|
+
return this.dduBinaryToBuffer(binaryParts.join(""), paddingBits);
|
|
299
344
|
}
|
|
300
345
|
decode(input, _options) {
|
|
301
346
|
return this.decodeToBuffer(input, _options).toString(this.encoding);
|
|
@@ -15,6 +15,10 @@ export declare class Ddu64 extends BaseDdu {
|
|
|
15
15
|
private readonly indexToBinaryCache;
|
|
16
16
|
constructor(dduChar?: string[] | string, paddingChar?: string, dduOptions?: DduConstructorOptions);
|
|
17
17
|
private normalizeCharSet;
|
|
18
|
+
private resolveInitialCharSet;
|
|
19
|
+
private getFallbackCharSet;
|
|
20
|
+
private shouldUsePowerOfTwo;
|
|
21
|
+
private getCharSetOrThrow;
|
|
18
22
|
private getBinaryFromIndex;
|
|
19
23
|
private validateCombinationDuplicates;
|
|
20
24
|
encode(input: Buffer | string, _options?: DduOptions): string;
|
|
@@ -17,75 +17,7 @@ export class Ddu64 extends BaseDdu {
|
|
|
17
17
|
constructor(dduChar, paddingChar, dduOptions) {
|
|
18
18
|
super();
|
|
19
19
|
const shouldThrowError = dduOptions?.useBuildErrorReturn ?? false;
|
|
20
|
-
const
|
|
21
|
-
const cs = getCharSet(symbol);
|
|
22
|
-
if (!cs)
|
|
23
|
-
throw new Error(`CharSet with symbol ${symbol} not found`);
|
|
24
|
-
return cs;
|
|
25
|
-
};
|
|
26
|
-
// charset 초기화
|
|
27
|
-
let charSet, padding, requiredLength, bitLength, isPredefined;
|
|
28
|
-
try {
|
|
29
|
-
const finalDduChar = dduChar ?? dduOptions?.dduChar;
|
|
30
|
-
const finalPadding = paddingChar ?? dduOptions?.paddingChar;
|
|
31
|
-
if (finalDduChar) {
|
|
32
|
-
// 커스텀 charset
|
|
33
|
-
if (!finalPadding) {
|
|
34
|
-
throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
|
|
35
|
-
}
|
|
36
|
-
// 문자열을 배열로 변환 (중복 제거 없이)
|
|
37
|
-
const arr = typeof finalDduChar === "string" ? [...finalDduChar.trim()] : finalDduChar;
|
|
38
|
-
// 중복 검사 (useBuildErrorReturn이 true일 때만)
|
|
39
|
-
if (shouldThrowError) {
|
|
40
|
-
const uniqueChars = new Set(arr);
|
|
41
|
-
if (uniqueChars.size !== arr.length) {
|
|
42
|
-
const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
|
|
43
|
-
throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
const len = dduOptions?.requiredLength ?? arr.length;
|
|
47
|
-
if (arr.length < len) {
|
|
48
|
-
throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
|
|
49
|
-
}
|
|
50
|
-
const usePow2 = dduOptions?.usePowerOfTwo === true || (dduOptions?.usePowerOfTwo === undefined && len > 0 && (len & (len - 1)) === 0);
|
|
51
|
-
if (usePow2) {
|
|
52
|
-
const exp = this.getLargestPowerOfTwoExponent(len);
|
|
53
|
-
const pow2Len = 1 << exp;
|
|
54
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, pow2Len), finalPadding, pow2Len, exp, false];
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, len), finalPadding, len, this.getBitLength(len), false];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else if (dduOptions?.dduSetSymbol) {
|
|
61
|
-
// 미리 정의된 charset
|
|
62
|
-
const cs = getCharSetFromSymbol(dduOptions.dduSetSymbol);
|
|
63
|
-
const usePow2 = dduOptions?.usePowerOfTwo === true || (dduOptions?.usePowerOfTwo === undefined && cs.maxRequiredLength > 0 && (cs.maxRequiredLength & (cs.maxRequiredLength - 1)) === 0);
|
|
64
|
-
if (usePow2) {
|
|
65
|
-
const exp = this.getLargestPowerOfTwoExponent(cs.maxRequiredLength);
|
|
66
|
-
const pow2Len = 1 << exp;
|
|
67
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet.slice(0, pow2Len), cs.paddingChar, pow2Len, exp, true];
|
|
68
|
-
}
|
|
69
|
-
else {
|
|
70
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
else {
|
|
74
|
-
// 기본 charset
|
|
75
|
-
const cs = getCharSetFromSymbol(dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.DDU);
|
|
76
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
if (shouldThrowError)
|
|
81
|
-
throw error;
|
|
82
|
-
// fallback
|
|
83
|
-
const fallbackSymbol = dduOptions?.dduSetSymbol ?? dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.ONECHARSET;
|
|
84
|
-
const cs = getCharSet(fallbackSymbol) ?? getCharSet(DduSetSymbol.ONECHARSET);
|
|
85
|
-
if (!cs)
|
|
86
|
-
throw new Error(`Critical: No fallback CharSet available`);
|
|
87
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
88
|
-
}
|
|
20
|
+
const { charSet, padding, requiredLength, bitLength, isPredefined, } = this.resolveInitialCharSet(dduChar, paddingChar, dduOptions, shouldThrowError);
|
|
89
21
|
// 정규화 및 검증
|
|
90
22
|
const normalized = this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
|
|
91
23
|
this.dduChar = normalized.charSet;
|
|
@@ -103,7 +35,13 @@ export class Ddu64 extends BaseDdu {
|
|
|
103
35
|
this.maxBinaryValue = 2 ** this.effectiveBitLength;
|
|
104
36
|
// 성능 최적화: 단일 구현 사용 (벤치마크 결과 Method2가 더 빠르고 안정적)
|
|
105
37
|
// Method2는 32비트 이상에서도 오버플로우 없이 정확한 결과 제공
|
|
106
|
-
this.binaryChunkToIntFn = (chunk) =>
|
|
38
|
+
this.binaryChunkToIntFn = (chunk) => {
|
|
39
|
+
let value = 0;
|
|
40
|
+
for (let i = 0; i < chunk.length; i++) {
|
|
41
|
+
value = value * 2 + (chunk.charCodeAt(i) & 1);
|
|
42
|
+
}
|
|
43
|
+
return value;
|
|
44
|
+
};
|
|
107
45
|
this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
|
|
108
46
|
if (this.charLength === 1 && !this.isPredefinedCharSet) {
|
|
109
47
|
this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
|
|
@@ -116,68 +54,180 @@ export class Ddu64 extends BaseDdu {
|
|
|
116
54
|
}
|
|
117
55
|
}
|
|
118
56
|
normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions) {
|
|
119
|
-
const applyFallback = () =>
|
|
120
|
-
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
return { charSet: cs.charSet, padding: cs.paddingChar, requiredLength: cs.maxRequiredLength, bitLength: cs.bitLength, isPredefined: true };
|
|
125
|
-
};
|
|
126
|
-
const validate = (condition, message) => {
|
|
127
|
-
if (condition) {
|
|
57
|
+
const applyFallback = () => this.getFallbackCharSet(dduOptions);
|
|
58
|
+
while (true) {
|
|
59
|
+
const ensure = (condition, message) => {
|
|
60
|
+
if (!condition)
|
|
61
|
+
return false;
|
|
128
62
|
if (shouldThrowError)
|
|
129
63
|
throw new Error(message);
|
|
130
|
-
|
|
131
|
-
|
|
64
|
+
({
|
|
65
|
+
charSet,
|
|
66
|
+
padding,
|
|
67
|
+
requiredLength,
|
|
68
|
+
bitLength,
|
|
69
|
+
isPredefined,
|
|
70
|
+
} = applyFallback());
|
|
132
71
|
return true;
|
|
72
|
+
};
|
|
73
|
+
const uniqueChars = new Set(charSet);
|
|
74
|
+
if (uniqueChars.size !== charSet.length) {
|
|
75
|
+
const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
|
|
76
|
+
const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[
|
|
77
|
+
...new Set(duplicates),
|
|
78
|
+
].join(", ")}]`;
|
|
79
|
+
if (shouldThrowError)
|
|
80
|
+
throw new Error(errorMsg);
|
|
81
|
+
charSet = Array.from(uniqueChars);
|
|
82
|
+
if (!isPredefined) {
|
|
83
|
+
requiredLength = charSet.length;
|
|
84
|
+
}
|
|
133
85
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// 중복 제거 (모든 charset에 적용)
|
|
137
|
-
const uniqueChars = new Set(charSet);
|
|
138
|
-
if (uniqueChars.size !== charSet.length) {
|
|
139
|
-
const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
|
|
140
|
-
const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`;
|
|
141
|
-
if (shouldThrowError)
|
|
142
|
-
throw new Error(errorMsg);
|
|
143
|
-
// 중복 제거 후 계속 진행
|
|
144
|
-
charSet = Array.from(uniqueChars);
|
|
145
|
-
if (!isPredefined) {
|
|
146
|
-
requiredLength = charSet.length;
|
|
86
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters. Required: ${requiredLength}, Provided: ${charSet.length}`)) {
|
|
87
|
+
continue;
|
|
147
88
|
}
|
|
89
|
+
if (ensure(requiredLength < 2, `[Ddu64 normalizeCharSet] At least 2 unique characters are required. Provided: ${requiredLength}`)) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (ensure(bitLength <= 0, `[Ddu64 normalizeCharSet] Invalid bit length (${bitLength}) for charset size ${requiredLength}`)) {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset. Required: ${requiredLength} characters`)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
let charLength = charSet[0].length;
|
|
99
|
+
const invalidIndex = charSet.findIndex((char) => char.length !== charLength);
|
|
100
|
+
if (invalidIndex !== -1) {
|
|
101
|
+
if (shouldThrowError) {
|
|
102
|
+
throw new Error(`[Ddu64 normalizeCharSet] Inconsistent character length. Expected: ${charLength}, but character at index ${invalidIndex} ("${charSet[invalidIndex]}") has length ${charSet[invalidIndex].length}`);
|
|
103
|
+
}
|
|
104
|
+
charSet = charSet.filter((char) => char.length === charLength);
|
|
105
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after filtering. Required: ${requiredLength}, Remaining: ${charSet.length}`)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after filtering`)) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
charLength = charSet[0].length;
|
|
112
|
+
}
|
|
113
|
+
if (ensure(padding.length !== charLength, `[Ddu64 normalizeCharSet] Padding character length mismatch. Expected: ${charLength}, Got: ${padding.length} (padding: "${padding}")`)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (charSet.includes(padding)) {
|
|
117
|
+
if (shouldThrowError) {
|
|
118
|
+
throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
|
|
119
|
+
}
|
|
120
|
+
charSet = charSet.filter((char) => char !== padding);
|
|
121
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`)) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (ensure(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after removing padding conflict`)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
charLength = charSet[0].length;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
charSet: charSet.slice(0, requiredLength),
|
|
131
|
+
padding,
|
|
132
|
+
requiredLength,
|
|
133
|
+
bitLength,
|
|
134
|
+
isPredefined,
|
|
135
|
+
charLength,
|
|
136
|
+
};
|
|
148
137
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
138
|
+
}
|
|
139
|
+
resolveInitialCharSet(dduChar, paddingChar, dduOptions, shouldThrowError) {
|
|
140
|
+
const finalize = (set, padding, length, baseBitLength, predefined = false) => {
|
|
141
|
+
const usePow2 = this.shouldUsePowerOfTwo(length, dduOptions?.usePowerOfTwo);
|
|
142
|
+
if (usePow2 && length > 0) {
|
|
143
|
+
const exponent = this.getLargestPowerOfTwoExponent(length);
|
|
144
|
+
const pow2Length = 1 << exponent;
|
|
145
|
+
return {
|
|
146
|
+
charSet: set.slice(0, pow2Length),
|
|
147
|
+
padding,
|
|
148
|
+
requiredLength: pow2Length,
|
|
149
|
+
bitLength: exponent,
|
|
150
|
+
isPredefined: predefined,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const computedBitLength = baseBitLength ?? (length > 0 ? this.getBitLength(length) : 0);
|
|
154
|
+
return {
|
|
155
|
+
charSet: set.slice(0, length),
|
|
156
|
+
padding,
|
|
157
|
+
requiredLength: length,
|
|
158
|
+
bitLength: computedBitLength,
|
|
159
|
+
isPredefined: predefined,
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
const fallback = () => this.getFallbackCharSet(dduOptions);
|
|
163
|
+
try {
|
|
164
|
+
const finalDduChar = dduChar ?? dduOptions?.dduChar;
|
|
165
|
+
const finalPadding = paddingChar ?? dduOptions?.paddingChar;
|
|
166
|
+
if (finalDduChar) {
|
|
167
|
+
if (!finalPadding) {
|
|
168
|
+
throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
|
|
169
|
+
}
|
|
170
|
+
const arr = typeof finalDduChar === "string"
|
|
171
|
+
? [...finalDduChar.trim()]
|
|
172
|
+
: [...finalDduChar];
|
|
173
|
+
if (shouldThrowError) {
|
|
174
|
+
const uniqueChars = new Set(arr);
|
|
175
|
+
if (uniqueChars.size !== arr.length) {
|
|
176
|
+
const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
|
|
177
|
+
throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[
|
|
178
|
+
...new Set(duplicates),
|
|
179
|
+
].join(", ")}]`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const len = dduOptions?.requiredLength ?? arr.length;
|
|
183
|
+
if (arr.length < len) {
|
|
184
|
+
throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
|
|
185
|
+
}
|
|
186
|
+
return finalize(arr, finalPadding, len, undefined, false);
|
|
187
|
+
}
|
|
188
|
+
if (dduOptions?.dduSetSymbol) {
|
|
189
|
+
const cs = this.getCharSetOrThrow(dduOptions.dduSetSymbol);
|
|
190
|
+
return finalize(cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true);
|
|
191
|
+
}
|
|
192
|
+
const defaultSymbol = dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.DDU;
|
|
193
|
+
const cs = this.getCharSetOrThrow(defaultSymbol);
|
|
194
|
+
return finalize(cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true);
|
|
155
195
|
}
|
|
156
|
-
|
|
157
|
-
|
|
196
|
+
catch (error) {
|
|
197
|
+
if (shouldThrowError)
|
|
198
|
+
throw error;
|
|
199
|
+
return fallback();
|
|
158
200
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
201
|
+
}
|
|
202
|
+
getFallbackCharSet(dduOptions) {
|
|
203
|
+
const fallbackSymbol = dduOptions?.dduSetSymbol ??
|
|
204
|
+
dduDefaultConstructorOptions.dduSetSymbol ??
|
|
205
|
+
DduSetSymbol.ONECHARSET;
|
|
206
|
+
const cs = getCharSet(fallbackSymbol) ?? getCharSet(DduSetSymbol.ONECHARSET);
|
|
207
|
+
if (!cs)
|
|
208
|
+
throw new Error(`Critical: No fallback CharSet available`);
|
|
209
|
+
return {
|
|
210
|
+
charSet: cs.charSet,
|
|
211
|
+
padding: cs.paddingChar,
|
|
212
|
+
requiredLength: cs.maxRequiredLength,
|
|
213
|
+
bitLength: cs.bitLength,
|
|
214
|
+
isPredefined: true,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
shouldUsePowerOfTwo(length, preference) {
|
|
218
|
+
if (preference === true) {
|
|
219
|
+
return length > 0;
|
|
169
220
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (new Set(charSet).has(padding)) {
|
|
173
|
-
if (shouldThrowError) {
|
|
174
|
-
throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
|
|
175
|
-
}
|
|
176
|
-
charSet = charSet.filter(char => char !== padding);
|
|
177
|
-
validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`);
|
|
178
|
-
charLength = charSet[0]?.length ?? 0;
|
|
221
|
+
if (preference === false) {
|
|
222
|
+
return false;
|
|
179
223
|
}
|
|
180
|
-
return
|
|
224
|
+
return length > 0 && (length & (length - 1)) === 0;
|
|
225
|
+
}
|
|
226
|
+
getCharSetOrThrow(symbol) {
|
|
227
|
+
const cs = getCharSet(symbol);
|
|
228
|
+
if (!cs)
|
|
229
|
+
throw new Error(`CharSet with symbol ${symbol} not found`);
|
|
230
|
+
return cs;
|
|
181
231
|
}
|
|
182
232
|
getBinaryFromIndex(index) {
|
|
183
233
|
if (index < 0 || index >= this.maxBinaryValue) {
|
|
@@ -217,27 +267,23 @@ export class Ddu64 extends BaseDdu {
|
|
|
217
267
|
encode(input, _options) {
|
|
218
268
|
// options는 구버전 호환성을 위해 유지하지만 사용하지 않음
|
|
219
269
|
const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
|
|
220
|
-
|
|
221
|
-
const dduLength = this.dduChar.length;
|
|
222
|
-
const effectiveBitLength = this.effectiveBitLength;
|
|
223
|
-
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
|
|
224
|
-
// 문자열 연결 최적화: Array + join 사용
|
|
270
|
+
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, this.effectiveBitLength);
|
|
225
271
|
const resultParts = new Array(dduBinary.length);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const charInt = this.binaryChunkToIntFn(binaryChunk);
|
|
230
|
-
if (!this.usePowerOfTwo) {
|
|
231
|
-
// 가변 길이 조합 인코딩 (멀티바이트 문자도 지원)
|
|
232
|
-
const quotient = Math.floor(charInt / dduLength);
|
|
233
|
-
const remainder = charInt % dduLength;
|
|
234
|
-
resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
// 고정 길이 직접 매핑
|
|
272
|
+
if (this.usePowerOfTwo) {
|
|
273
|
+
for (let i = 0; i < dduBinary.length; i++) {
|
|
274
|
+
const charInt = this.binaryChunkToIntFn(dduBinary[i]);
|
|
238
275
|
resultParts[i] = this.dduChar[charInt];
|
|
239
276
|
}
|
|
240
277
|
}
|
|
278
|
+
else {
|
|
279
|
+
const dduLength = this.dduChar.length;
|
|
280
|
+
for (let i = 0; i < dduBinary.length; i++) {
|
|
281
|
+
const value = this.binaryChunkToIntFn(dduBinary[i]);
|
|
282
|
+
const quotient = Math.floor(value / dduLength);
|
|
283
|
+
const remainder = value % dduLength;
|
|
284
|
+
resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
241
287
|
let resultString = resultParts.join("");
|
|
242
288
|
// 패딩 비트 정보를 padChar + 패딩비트수 형태로 추가
|
|
243
289
|
if (padding > 0) {
|
|
@@ -266,13 +312,14 @@ export class Ddu64 extends BaseDdu {
|
|
|
266
312
|
input = input.substring(0, padCharIndex);
|
|
267
313
|
}
|
|
268
314
|
}
|
|
269
|
-
|
|
315
|
+
const binaryParts = [];
|
|
316
|
+
const charLength = this.charLength;
|
|
270
317
|
const dduLength = this.dduChar.length;
|
|
271
318
|
if (!this.usePowerOfTwo) {
|
|
272
|
-
const chunkSize =
|
|
319
|
+
const chunkSize = charLength * 2;
|
|
273
320
|
for (let i = 0; i < input.length; i += chunkSize) {
|
|
274
|
-
const firstChar = input.slice(i, i +
|
|
275
|
-
const secondChar = input.slice(i +
|
|
321
|
+
const firstChar = input.slice(i, i + charLength);
|
|
322
|
+
const secondChar = input.slice(i + charLength, i + chunkSize);
|
|
276
323
|
const firstIndex = this.dduBinaryLookup.get(firstChar);
|
|
277
324
|
const secondIndex = this.dduBinaryLookup.get(secondChar);
|
|
278
325
|
if (firstIndex === undefined || secondIndex === undefined) {
|
|
@@ -283,23 +330,23 @@ export class Ddu64 extends BaseDdu {
|
|
|
283
330
|
if (value >= this.maxBinaryValue) {
|
|
284
331
|
throw new Error(`[Ddu64 decode] Invalid character combination detected. Calculated value ${value} exceeds binary range ${this.maxBinaryValue - 1}.`);
|
|
285
332
|
}
|
|
286
|
-
|
|
333
|
+
binaryParts.push(this.getBinaryFromIndex(value));
|
|
287
334
|
}
|
|
288
335
|
}
|
|
289
336
|
else {
|
|
290
|
-
for (let i = 0; i < input.length; i +=
|
|
291
|
-
const charChunk = input.slice(i, i +
|
|
337
|
+
for (let i = 0; i < input.length; i += charLength) {
|
|
338
|
+
const charChunk = input.slice(i, i + charLength);
|
|
292
339
|
const charIndex = this.dduBinaryLookup.get(charChunk);
|
|
293
340
|
if (charIndex === undefined) {
|
|
294
|
-
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${
|
|
341
|
+
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${charLength}`);
|
|
295
342
|
}
|
|
296
343
|
if (charIndex >= this.maxBinaryValue) {
|
|
297
344
|
throw new Error(`[Ddu64 decode] Invalid binary index ${charIndex}. Allowed range: 0-${this.maxBinaryValue - 1}`);
|
|
298
345
|
}
|
|
299
|
-
|
|
346
|
+
binaryParts.push(this.getBinaryFromIndex(charIndex));
|
|
300
347
|
}
|
|
301
348
|
}
|
|
302
|
-
return this.dduBinaryToBuffer(
|
|
349
|
+
return this.dduBinaryToBuffer(binaryParts.join(""), paddingBits);
|
|
303
350
|
}
|
|
304
351
|
decode(input, _options) {
|
|
305
352
|
return this.decodeToBuffer(input, _options).toString(this.encoding);
|