@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.
@@ -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, _c, _d, _e, _f, _g;
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 getCharSetFromSymbol = (symbol) => {
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 = (_g = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _g !== void 0 ? _g : this.defaultEncoding;
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.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : this.getBitLength(dduLength);
82
- this.binaryChunkToIntFn = this.effectiveBitLength <= 26
83
- ? (chunk) => chunk.split('').reduce((v, c) => (v << 1) | (c.charCodeAt(0) & 1), 0)
84
- : (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
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 >= 2 && !this.isPredefinedCharSet) {
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
- const size = 1 << this.effectiveBitLength;
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
- var _a, _b, _c, _d, _e, _f;
99
- const applyFallback = () => {
100
- var _a, _b, _c;
101
- 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;
102
- const cs = (_c = (0, charSets_1.getCharSet)(fallbackSymbol)) !== null && _c !== void 0 ? _c : (0, charSets_1.getCharSet)(types_1.DduSetSymbol.ONECHARSET);
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
- const fb = applyFallback();
112
- [charSet, padding, requiredLength, bitLength, isPredefined] = [fb.charSet, fb.padding, fb.requiredLength, fb.bitLength, fb.isPredefined];
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(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
73
+ throw new Error(errorMsg);
123
74
  charSet = Array.from(uniqueChars);
124
- requiredLength = charSet.length;
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
- if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
129
- return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
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
- let charLength = (_b = (_a = charSet[0]) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0;
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 new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
137
- charSet = charSet.filter(char => char.length === charLength);
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
- validate(!charSet.length, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: 0`);
142
- validate(padding.length !== charLength, `Padding character must have length ${charLength}, got ${padding.length}`);
143
- if (new Set(charSet).has(padding)) {
144
- if (shouldThrowError)
145
- throw new Error(`Padding character "${padding}" cannot be in the character set`);
146
- charSet = charSet.filter(char => char !== padding);
147
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
148
- charLength = (_f = (_e = charSet[0]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0;
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 { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
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
- if (allStrings.has(padChar))
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
- for (let i = 0; i < dduBinary.length; i++) {
208
- const binaryChunk = dduBinary[i];
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
- decode(input, _options) {
229
- // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
230
- // 패딩 정보 추출
288
+ decodeToBuffer(input, _options) {
231
289
  let paddingBits = 0;
232
- const padCharIndex = input.indexOf(this.paddingChar);
233
- if (padCharIndex >= 0) {
234
- const paddingStr = input.substring(padCharIndex + this.paddingChar.length);
235
- paddingBits = parseInt(paddingStr) || 0;
236
- input = input.substring(0, padCharIndex);
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
- let dduBinary = "";
239
- // 생성자에서 설정된 값 사용
309
+ const binaryParts = [];
310
+ const charLength = this.charLength;
240
311
  const dduLength = this.dduChar.length;
241
- const effectiveBitLength = this.effectiveBitLength;
242
- if (this.charLength === 1 && !this.usePowerOfTwo) {
243
- // 가변 길이 조합 디코딩 (2개씩 읽음)
244
- for (let i = 0; i < input.length; i += 2) {
245
- const firstChar = input[i];
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
- throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
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
- dduBinary += this.getBinaryFromIndex(value);
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
- // 고정 길이 직접 매핑 (charLength만큼씩 읽음)
257
- for (let i = 0; i < input.length; i += this.charLength) {
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
- dduBinary += this.getBinaryFromIndex(charIndex);
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
- const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
266
- return decoded.toString(this.encoding);
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
  }
@@ -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);