@ddunigma/node 1.1.6 → 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/README.md +44 -0
- package/dist/cjs/base/BaseDdu.d.ts +13 -24
- package/dist/cjs/base/BaseDdu.js +0 -24
- package/dist/cjs/encoders/Ddu64.d.ts +18 -0
- package/dist/cjs/encoders/Ddu64.js +266 -172
- package/dist/mjs/base/BaseDdu.d.ts +13 -24
- package/dist/mjs/base/BaseDdu.js +0 -24
- package/dist/mjs/encoders/Ddu64.d.ts +18 -0
- package/dist/mjs/encoders/Ddu64.js +265 -168
- package/package.json +4 -3
|
@@ -6,158 +6,237 @@ 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("paddingChar is required when dduChar or dduOptions.dduChar is provided");
|
|
28
|
-
const arr = typeof finalDduChar === "string" ? [...new Set(finalDduChar.trim())] : finalDduChar;
|
|
29
|
-
const len = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.requiredLength) !== null && _b !== void 0 ? _b : arr.length;
|
|
30
|
-
if (arr.length < len)
|
|
31
|
-
throw new Error(`dduChar must be at least ${len} characters long. Provided: ${arr.length}`);
|
|
32
|
-
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);
|
|
33
|
-
if (usePow2) {
|
|
34
|
-
const exp = this.getLargestPowerOfTwoExponent(len);
|
|
35
|
-
const pow2Len = 1 << exp;
|
|
36
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, pow2Len), finalPadding, pow2Len, exp, false];
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [arr.slice(0, len), finalPadding, len, this.getBitLength(len), false];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
else if (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.dduSetSymbol) {
|
|
43
|
-
// 미리 정의된 charset
|
|
44
|
-
const cs = getCharSetFromSymbol(dduOptions.dduSetSymbol);
|
|
45
|
-
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);
|
|
46
|
-
if (usePow2) {
|
|
47
|
-
const exp = this.getLargestPowerOfTwoExponent(cs.maxRequiredLength);
|
|
48
|
-
const pow2Len = 1 << exp;
|
|
49
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet.slice(0, pow2Len), cs.paddingChar, pow2Len, exp, true];
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
else {
|
|
56
|
-
// 기본 charset
|
|
57
|
-
const cs = getCharSetFromSymbol((_c = types_1.dduDefaultConstructorOptions.dduSetSymbol) !== null && _c !== void 0 ? _c : types_1.DduSetSymbol.DDU);
|
|
58
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
if (shouldThrowError)
|
|
63
|
-
throw error;
|
|
64
|
-
// fallback
|
|
65
|
-
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;
|
|
66
|
-
const cs = (_f = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _f !== void 0 ? _f : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
|
|
67
|
-
if (!cs)
|
|
68
|
-
throw new Error(`Critical: No fallback CharSet available`);
|
|
69
|
-
[charSet, padding, requiredLength, bitLength, isPredefined] = [cs.charSet, cs.paddingChar, cs.maxRequiredLength, cs.bitLength, true];
|
|
70
|
-
}
|
|
13
|
+
const { charSet, padding, requiredLength, bitLength, isPredefined, } = this.resolveInitialCharSet(dduChar, paddingChar, dduOptions, shouldThrowError);
|
|
71
14
|
// 정규화 및 검증
|
|
72
15
|
const normalized = this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
|
|
73
16
|
this.dduChar = normalized.charSet;
|
|
74
17
|
this.paddingChar = normalized.padding;
|
|
75
18
|
this.charLength = normalized.charLength;
|
|
76
|
-
this.bitLength = normalized.bitLength;
|
|
77
19
|
this.isPredefinedCharSet = normalized.isPredefined;
|
|
78
|
-
this.encoding = (
|
|
20
|
+
this.encoding = (_b = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _b !== void 0 ? _b : this.defaultEncoding;
|
|
79
21
|
const dduLength = this.dduChar.length;
|
|
22
|
+
const recalculatedBitLength = this.getBitLength(dduLength);
|
|
80
23
|
this.usePowerOfTwo = dduLength > 0 && (dduLength & (dduLength - 1)) === 0;
|
|
81
|
-
this.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
24
|
+
this.bitLength = this.usePowerOfTwo
|
|
25
|
+
? this.getLargestPowerOfTwoExponent(dduLength)
|
|
26
|
+
: recalculatedBitLength;
|
|
27
|
+
this.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : recalculatedBitLength;
|
|
28
|
+
this.maxBinaryValue = Math.pow(2, this.effectiveBitLength);
|
|
29
|
+
// 성능 최적화: 단일 구현 사용 (벤치마크 결과 Method2가 더 빠르고 안정적)
|
|
30
|
+
// Method2는 32비트 이상에서도 오버플로우 없이 정확한 결과 제공
|
|
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
|
+
};
|
|
85
38
|
this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
|
|
86
|
-
if (this.charLength
|
|
39
|
+
if (this.charLength === 1 && !this.isPredefinedCharSet) {
|
|
87
40
|
this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
|
|
88
41
|
}
|
|
89
42
|
if (this.effectiveBitLength <= 16) {
|
|
90
|
-
|
|
91
|
-
this.indexToBinaryCache = Array.from({ length: size }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
|
|
43
|
+
this.indexToBinaryCache = Array.from({ length: this.maxBinaryValue }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
|
|
92
44
|
}
|
|
93
45
|
else {
|
|
94
46
|
this.indexToBinaryCache = null;
|
|
95
47
|
}
|
|
96
48
|
}
|
|
97
49
|
normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if (!cs)
|
|
104
|
-
throw new Error(`Critical: No fallback CharSet available`);
|
|
105
|
-
return { charSet: cs.charSet, padding: cs.paddingChar, requiredLength: cs.maxRequiredLength, bitLength: cs.bitLength, isPredefined: true };
|
|
106
|
-
};
|
|
107
|
-
const validate = (condition, message) => {
|
|
108
|
-
if (condition) {
|
|
50
|
+
const applyFallback = () => this.getFallbackCharSet(dduOptions);
|
|
51
|
+
while (true) {
|
|
52
|
+
const ensure = (condition, message) => {
|
|
53
|
+
if (!condition)
|
|
54
|
+
return false;
|
|
109
55
|
if (shouldThrowError)
|
|
110
56
|
throw new Error(message);
|
|
111
|
-
|
|
112
|
-
|
|
57
|
+
({
|
|
58
|
+
charSet,
|
|
59
|
+
padding,
|
|
60
|
+
requiredLength,
|
|
61
|
+
bitLength,
|
|
62
|
+
isPredefined,
|
|
63
|
+
} = applyFallback());
|
|
113
64
|
return true;
|
|
114
|
-
}
|
|
115
|
-
return false;
|
|
116
|
-
};
|
|
117
|
-
// 중복 제거 (커스텀 charset만)
|
|
118
|
-
if (!isPredefined) {
|
|
65
|
+
};
|
|
119
66
|
const uniqueChars = new Set(charSet);
|
|
120
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(", ")}]`;
|
|
121
72
|
if (shouldThrowError)
|
|
122
|
-
throw new Error(
|
|
73
|
+
throw new Error(errorMsg);
|
|
123
74
|
charSet = Array.from(uniqueChars);
|
|
124
|
-
|
|
75
|
+
if (!isPredefined) {
|
|
76
|
+
requiredLength = charSet.length;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (ensure(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters. Required: ${requiredLength}, Provided: ${charSet.length}`)) {
|
|
80
|
+
continue;
|
|
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;
|
|
125
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
|
+
};
|
|
126
130
|
}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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);
|
|
130
189
|
}
|
|
131
|
-
|
|
132
|
-
validate(charLength === 0, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
|
|
133
|
-
const invalidIndex = charSet.findIndex(char => char.length !== charLength);
|
|
134
|
-
if (invalidIndex !== -1) {
|
|
190
|
+
catch (error) {
|
|
135
191
|
if (shouldThrowError)
|
|
136
|
-
throw
|
|
137
|
-
|
|
138
|
-
validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
|
|
139
|
-
charLength = (_d = (_c = charSet[0]) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
|
|
192
|
+
throw error;
|
|
193
|
+
return fallback();
|
|
140
194
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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;
|
|
213
|
+
}
|
|
214
|
+
if (preference === false) {
|
|
215
|
+
return false;
|
|
149
216
|
}
|
|
150
|
-
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;
|
|
151
224
|
}
|
|
152
225
|
getBinaryFromIndex(index) {
|
|
153
226
|
var _a, _b;
|
|
227
|
+
if (index < 0 || index >= this.maxBinaryValue) {
|
|
228
|
+
throw new Error(`[Ddu64] Binary index overflow. Received: ${index}, Allowed range: 0-${this.maxBinaryValue - 1}`);
|
|
229
|
+
}
|
|
154
230
|
return (_b = (_a = this.indexToBinaryCache) === null || _a === void 0 ? void 0 : _a[index]) !== null && _b !== void 0 ? _b : index.toString(2).padStart(this.effectiveBitLength, "0");
|
|
155
231
|
}
|
|
156
232
|
validateCombinationDuplicates(charSet, paddingChar, requiredLength) {
|
|
157
233
|
const charLength = charSet[0].length;
|
|
234
|
+
// 조합 충돌 검사는 단일 문자 집합이면서 비교적 작은 경우(<=256)에만 적용한다.
|
|
235
|
+
if (charLength !== 1 || requiredLength > 256) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
158
238
|
const allStrings = new Set([...charSet.slice(0, requiredLength), paddingChar]);
|
|
159
239
|
const limit = Math.min(charSet.length, requiredLength);
|
|
160
|
-
// 2개 조합 검사
|
|
161
240
|
for (let i = 0; i < limit; i++) {
|
|
162
241
|
for (let j = 0; j < limit; j++) {
|
|
163
242
|
const combo = charSet[i] + charSet[j];
|
|
@@ -167,57 +246,38 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
167
246
|
}
|
|
168
247
|
const charPad = charSet[i] + paddingChar;
|
|
169
248
|
const padChar = paddingChar + charSet[i];
|
|
170
|
-
if (allStrings.has(charPad))
|
|
249
|
+
if (allStrings.has(charPad)) {
|
|
171
250
|
throw new Error(`Combination conflict: "${charSet[i]}" + padding "${paddingChar}" = "${charPad}"`);
|
|
172
|
-
|
|
251
|
+
}
|
|
252
|
+
if (allStrings.has(padChar)) {
|
|
173
253
|
throw new Error(`Combination conflict: padding "${paddingChar}" + "${charSet[i]}" = "${padChar}"`);
|
|
254
|
+
}
|
|
174
255
|
}
|
|
175
|
-
// 패딩 + 패딩 조합 검사
|
|
176
256
|
const doublePad = paddingChar + paddingChar;
|
|
177
257
|
if (allStrings.has(doublePad)) {
|
|
178
258
|
throw new Error(`Combination conflict: double padding "${doublePad}" already exists`);
|
|
179
259
|
}
|
|
180
|
-
// 3개 조합 검사 (100개 이하만)
|
|
181
|
-
if (charLength >= 2 && requiredLength <= 100) {
|
|
182
|
-
for (let i = 0; i < limit; i++) {
|
|
183
|
-
for (let j = 0; j < limit; j++) {
|
|
184
|
-
for (let k = 0; k < limit; k++) {
|
|
185
|
-
const combo = charSet[i] + charSet[j] + charSet[k];
|
|
186
|
-
for (let start = 0; start <= combo.length - charLength; start++) {
|
|
187
|
-
const sub = combo.substring(start, start + charLength);
|
|
188
|
-
if (allStrings.has(sub) && sub !== charSet[i] && sub !== charSet[j] && sub !== charSet[k]) {
|
|
189
|
-
throw new Error(`Combination conflict: substring "${sub}" from "${charSet[i]}" + "${charSet[j]}" + "${charSet[k]}"`);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
260
|
}
|
|
197
261
|
encode(input, _options) {
|
|
198
262
|
// options는 구버전 호환성을 위해 유지하지만 사용하지 않음
|
|
199
263
|
const bufferInput = typeof input === "string" ? Buffer.from(input, this.encoding) : input;
|
|
200
|
-
|
|
201
|
-
const dduLength = this.dduChar.length;
|
|
202
|
-
const effectiveBitLength = this.effectiveBitLength;
|
|
203
|
-
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, effectiveBitLength);
|
|
204
|
-
// 문자열 연결 최적화: Array + join 사용
|
|
264
|
+
const { dduBinary, padding } = this.bufferToDduBinary(bufferInput, this.effectiveBitLength);
|
|
205
265
|
const resultParts = new Array(dduBinary.length);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const charInt = this.binaryChunkToIntFn(binaryChunk);
|
|
210
|
-
if (this.charLength === 1 && !this.usePowerOfTwo) {
|
|
211
|
-
// 가변 길이 조합 인코딩
|
|
212
|
-
const quotient = Math.floor(charInt / dduLength);
|
|
213
|
-
const remainder = charInt % dduLength;
|
|
214
|
-
resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
|
|
215
|
-
}
|
|
216
|
-
else {
|
|
217
|
-
// 고정 길이 직접 매핑
|
|
266
|
+
if (this.usePowerOfTwo) {
|
|
267
|
+
for (let i = 0; i < dduBinary.length; i++) {
|
|
268
|
+
const charInt = this.binaryChunkToIntFn(dduBinary[i]);
|
|
218
269
|
resultParts[i] = this.dduChar[charInt];
|
|
219
270
|
}
|
|
220
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
|
+
}
|
|
221
281
|
let resultString = resultParts.join("");
|
|
222
282
|
// 패딩 비트 정보를 padChar + 패딩비트수 형태로 추가
|
|
223
283
|
if (padding > 0) {
|
|
@@ -225,45 +285,79 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
|
|
|
225
285
|
}
|
|
226
286
|
return resultString;
|
|
227
287
|
}
|
|
228
|
-
|
|
229
|
-
// options는 구버전 호환성을 위해 유지하지만 사용하지 않음
|
|
230
|
-
// 패딩 정보 추출
|
|
288
|
+
decodeToBuffer(input, _options) {
|
|
231
289
|
let paddingBits = 0;
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
290
|
+
if (input.length >= this.paddingChar.length) {
|
|
291
|
+
const padCharIndex = input.lastIndexOf(this.paddingChar);
|
|
292
|
+
if (padCharIndex >= 0 &&
|
|
293
|
+
padCharIndex % this.charLength === 0 &&
|
|
294
|
+
padCharIndex + this.paddingChar.length <= input.length) {
|
|
295
|
+
const paddingSection = input.slice(padCharIndex + this.paddingChar.length);
|
|
296
|
+
if (paddingSection.length === 0) {
|
|
297
|
+
throw new Error(`[Ddu64 decode] Invalid padding format. Missing padding length after "${this.paddingChar}"`);
|
|
298
|
+
}
|
|
299
|
+
paddingBits = parseInt(paddingSection, 10);
|
|
300
|
+
if (isNaN(paddingBits) ||
|
|
301
|
+
paddingSection !== paddingBits.toString() ||
|
|
302
|
+
paddingBits < 0 ||
|
|
303
|
+
paddingBits >= this.effectiveBitLength) {
|
|
304
|
+
throw new Error(`[Ddu64 decode] Invalid padding format. Expected integer between 0 and ${this.effectiveBitLength - 1}, Got: "${paddingSection}"`);
|
|
305
|
+
}
|
|
306
|
+
input = input.substring(0, padCharIndex);
|
|
307
|
+
}
|
|
237
308
|
}
|
|
238
|
-
|
|
239
|
-
|
|
309
|
+
const binaryParts = [];
|
|
310
|
+
const charLength = this.charLength;
|
|
240
311
|
const dduLength = this.dduChar.length;
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
const secondChar = input[i + 1];
|
|
312
|
+
if (!this.usePowerOfTwo) {
|
|
313
|
+
const chunkSize = charLength * 2;
|
|
314
|
+
for (let i = 0; i < input.length; i += chunkSize) {
|
|
315
|
+
const firstChar = input.slice(i, i + charLength);
|
|
316
|
+
const secondChar = input.slice(i + charLength, i + chunkSize);
|
|
247
317
|
const firstIndex = this.dduBinaryLookup.get(firstChar);
|
|
248
318
|
const secondIndex = this.dduBinaryLookup.get(secondChar);
|
|
249
|
-
if (firstIndex === undefined || secondIndex === undefined)
|
|
250
|
-
|
|
319
|
+
if (firstIndex === undefined || secondIndex === undefined) {
|
|
320
|
+
const invalidChar = firstIndex === undefined ? firstChar : secondChar;
|
|
321
|
+
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${invalidChar}", Position: ${i}, Expected charset size: ${dduLength}`);
|
|
322
|
+
}
|
|
251
323
|
const value = firstIndex * dduLength + secondIndex;
|
|
252
|
-
|
|
324
|
+
if (value >= this.maxBinaryValue) {
|
|
325
|
+
throw new Error(`[Ddu64 decode] Invalid character combination detected. Calculated value ${value} exceeds binary range ${this.maxBinaryValue - 1}.`);
|
|
326
|
+
}
|
|
327
|
+
binaryParts.push(this.getBinaryFromIndex(value));
|
|
253
328
|
}
|
|
254
329
|
}
|
|
255
330
|
else {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const charChunk = input.slice(i, i + this.charLength);
|
|
331
|
+
for (let i = 0; i < input.length; i += charLength) {
|
|
332
|
+
const charChunk = input.slice(i, i + charLength);
|
|
259
333
|
const charIndex = this.dduBinaryLookup.get(charChunk);
|
|
260
|
-
if (charIndex === undefined)
|
|
261
|
-
throw new Error(`Invalid character: ${charChunk}`);
|
|
262
|
-
|
|
334
|
+
if (charIndex === undefined) {
|
|
335
|
+
throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${charLength}`);
|
|
336
|
+
}
|
|
337
|
+
if (charIndex >= this.maxBinaryValue) {
|
|
338
|
+
throw new Error(`[Ddu64 decode] Invalid binary index ${charIndex}. Allowed range: 0-${this.maxBinaryValue - 1}`);
|
|
339
|
+
}
|
|
340
|
+
binaryParts.push(this.getBinaryFromIndex(charIndex));
|
|
263
341
|
}
|
|
264
342
|
}
|
|
265
|
-
|
|
266
|
-
|
|
343
|
+
return this.dduBinaryToBuffer(binaryParts.join(""), paddingBits);
|
|
344
|
+
}
|
|
345
|
+
decode(input, _options) {
|
|
346
|
+
return this.decodeToBuffer(input, _options).toString(this.encoding);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* 테스트 및 디버깅용 getter 메서드
|
|
350
|
+
* 인코더의 내부 상태 정보를 반환
|
|
351
|
+
*/
|
|
352
|
+
getCharSetInfo() {
|
|
353
|
+
return {
|
|
354
|
+
charSet: [...this.dduChar],
|
|
355
|
+
paddingChar: this.paddingChar,
|
|
356
|
+
charLength: this.charLength,
|
|
357
|
+
bitLength: this.bitLength,
|
|
358
|
+
usePowerOfTwo: this.usePowerOfTwo,
|
|
359
|
+
encoding: this.encoding,
|
|
360
|
+
};
|
|
267
361
|
}
|
|
268
362
|
}
|
|
269
363
|
exports.Ddu64 = Ddu64;
|
|
@@ -1,38 +1,27 @@
|
|
|
1
1
|
import { DduOptions, BufferToDduBinaryResult } from "../types";
|
|
2
2
|
export declare abstract class BaseDdu {
|
|
3
3
|
protected readonly defaultEncoding: BufferEncoding;
|
|
4
|
-
/**
|
|
5
|
-
* 이진수 변환 룩업 테이블 (0-255 -> 8비트 이진 문자열)
|
|
6
|
-
*/
|
|
7
4
|
protected readonly binaryLookup: string[];
|
|
8
|
-
/**
|
|
9
|
-
* 정규표현식 특수문자 이스케이프
|
|
10
|
-
*/
|
|
11
5
|
protected escapeRegExp(str: string): string;
|
|
12
|
-
/**
|
|
13
|
-
* 문자열을 지정된 길이로 분할하는 제너레이터
|
|
14
|
-
*/
|
|
15
6
|
protected splitString(s: string, length: number): Generator<string>;
|
|
16
|
-
/**
|
|
17
|
-
* 2의 거듭제곱 길이 계산
|
|
18
|
-
*/
|
|
19
7
|
protected getLargestPowerOfTwo(n: number): number;
|
|
20
|
-
/**
|
|
21
|
-
* 2의 거듭제곱 지수 계산
|
|
22
|
-
*/
|
|
23
8
|
protected getLargestPowerOfTwoExponent(n: number): number;
|
|
24
|
-
/**
|
|
25
|
-
* 비트 길이 계산
|
|
26
|
-
*/
|
|
27
9
|
protected getBitLength(setLength: number): number;
|
|
28
|
-
/**
|
|
29
|
-
* Buffer를 DDU Binary 배열로 변환
|
|
30
|
-
*/
|
|
31
10
|
protected bufferToDduBinary(input: Buffer, bitLength: number): BufferToDduBinaryResult;
|
|
32
|
-
/**
|
|
33
|
-
* DDU Binary를 Buffer로 변환
|
|
34
|
-
*/
|
|
35
11
|
protected dduBinaryToBuffer(decodedBin: string, paddingBits: number): Buffer;
|
|
36
12
|
abstract encode(input: Buffer | string, options?: DduOptions): string;
|
|
13
|
+
abstract decodeToBuffer(input: string, options?: DduOptions): Buffer;
|
|
37
14
|
abstract decode(input: string, options?: DduOptions): string;
|
|
15
|
+
/**
|
|
16
|
+
* 테스트 및 디버깅용 추상 메서드
|
|
17
|
+
* 구현 클래스의 내부 상태 정보를 반환
|
|
18
|
+
*/
|
|
19
|
+
abstract getCharSetInfo(): {
|
|
20
|
+
charSet: string[];
|
|
21
|
+
paddingChar: string;
|
|
22
|
+
charLength: number;
|
|
23
|
+
bitLength: number;
|
|
24
|
+
usePowerOfTwo: boolean;
|
|
25
|
+
encoding: BufferEncoding;
|
|
26
|
+
};
|
|
38
27
|
}
|
package/dist/mjs/base/BaseDdu.js
CHANGED
|
@@ -1,44 +1,23 @@
|
|
|
1
1
|
export class BaseDdu {
|
|
2
2
|
defaultEncoding = "utf-8";
|
|
3
|
-
/**
|
|
4
|
-
* 이진수 변환 룩업 테이블 (0-255 -> 8비트 이진 문자열)
|
|
5
|
-
*/
|
|
6
3
|
binaryLookup = Array.from({ length: 256 }, (_, i) => i.toString(2).padStart(8, "0"));
|
|
7
|
-
/**
|
|
8
|
-
* 정규표현식 특수문자 이스케이프
|
|
9
|
-
*/
|
|
10
4
|
escapeRegExp(str) {
|
|
11
5
|
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
12
6
|
}
|
|
13
|
-
/**
|
|
14
|
-
* 문자열을 지정된 길이로 분할하는 제너레이터
|
|
15
|
-
*/
|
|
16
7
|
*splitString(s, length) {
|
|
17
8
|
for (let i = 0; i < s.length; i += length) {
|
|
18
9
|
yield s.slice(i, Math.min(i + length, s.length));
|
|
19
10
|
}
|
|
20
11
|
}
|
|
21
|
-
/**
|
|
22
|
-
* 2의 거듭제곱 길이 계산
|
|
23
|
-
*/
|
|
24
12
|
getLargestPowerOfTwo(n) {
|
|
25
13
|
return 2 ** Math.floor(Math.log2(n));
|
|
26
14
|
}
|
|
27
|
-
/**
|
|
28
|
-
* 2의 거듭제곱 지수 계산
|
|
29
|
-
*/
|
|
30
15
|
getLargestPowerOfTwoExponent(n) {
|
|
31
16
|
return Math.floor(Math.log2(n));
|
|
32
17
|
}
|
|
33
|
-
/**
|
|
34
|
-
* 비트 길이 계산
|
|
35
|
-
*/
|
|
36
18
|
getBitLength(setLength) {
|
|
37
19
|
return Math.ceil(Math.log2(setLength));
|
|
38
20
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Buffer를 DDU Binary 배열로 변환
|
|
41
|
-
*/
|
|
42
21
|
bufferToDduBinary(input, bitLength) {
|
|
43
22
|
// 성능 최적화: reduce 대신 for loop + join 사용
|
|
44
23
|
if (input.length === 0) {
|
|
@@ -56,9 +35,6 @@ export class BaseDdu {
|
|
|
56
35
|
}
|
|
57
36
|
return { dduBinary, padding };
|
|
58
37
|
}
|
|
59
|
-
/**
|
|
60
|
-
* DDU Binary를 Buffer로 변환
|
|
61
|
-
*/
|
|
62
38
|
dduBinaryToBuffer(decodedBin, paddingBits) {
|
|
63
39
|
if (paddingBits > 0) {
|
|
64
40
|
decodedBin = decodedBin.slice(0, -paddingBits);
|