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