@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.
@@ -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 getCharSetFromSymbol = (symbol) => {
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.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : this.getBitLength(dduLength);
88
- this.binaryChunkToIntFn = this.effectiveBitLength <= 26
89
- ? (chunk) => chunk.split('').reduce((v, c) => (v << 1) | (c.charCodeAt(0) & 1), 0)
90
- : (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
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 >= 2 && !this.isPredefinedCharSet) {
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
- const size = 1 << this.effectiveBitLength;
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
- const fallbackSymbol = dduOptions?.dduSetSymbol ?? dduDefaultConstructorOptions.dduSetSymbol ?? DduSetSymbol.ONECHARSET;
106
- const cs = getCharSet(fallbackSymbol) ?? getCharSet(DduSetSymbol.ONECHARSET);
107
- if (!cs)
108
- throw new Error(`Critical: No fallback CharSet available`);
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
- const fb = applyFallback();
116
- [charSet, padding, requiredLength, bitLength, isPredefined] = [fb.charSet, fb.padding, fb.requiredLength, fb.bitLength, fb.isPredefined];
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(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
80
+ throw new Error(errorMsg);
127
81
  charSet = Array.from(uniqueChars);
128
- requiredLength = charSet.length;
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
- if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
133
- return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
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
- let charLength = charSet[0]?.length ?? 0;
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 new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
141
- charSet = charSet.filter(char => char.length === charLength);
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
- validate(!charSet.length, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: 0`);
146
- validate(padding.length !== charLength, `Padding character must have length ${charLength}, got ${padding.length}`);
147
- if (new Set(charSet).has(padding)) {
148
- if (shouldThrowError)
149
- throw new Error(`Padding character "${padding}" cannot be in the character set`);
150
- charSet = charSet.filter(char => char !== padding);
151
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
152
- charLength = charSet[0]?.length ?? 0;
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 { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
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
- if (allStrings.has(padChar))
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
- for (let i = 0; i < dduBinary.length; i++) {
211
- const binaryChunk = dduBinary[i];
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
- decode(input, _options) {
232
- // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
233
- // 패딩 정보 추출
294
+ decodeToBuffer(input, _options) {
234
295
  let paddingBits = 0;
235
- const padCharIndex = input.indexOf(this.paddingChar);
236
- if (padCharIndex >= 0) {
237
- const paddingStr = input.substring(padCharIndex + this.paddingChar.length);
238
- paddingBits = parseInt(paddingStr) || 0;
239
- input = input.substring(0, padCharIndex);
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
- let dduBinary = "";
242
- // 생성자에서 설정된 값 사용
315
+ const binaryParts = [];
316
+ const charLength = this.charLength;
243
317
  const dduLength = this.dduChar.length;
244
- const effectiveBitLength = this.effectiveBitLength;
245
- if (this.charLength === 1 && !this.usePowerOfTwo) {
246
- // 가변 길이 조합 디코딩 (2개씩 읽음)
247
- for (let i = 0; i < input.length; i += 2) {
248
- const firstChar = input[i];
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
- throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
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
- dduBinary += this.getBinaryFromIndex(value);
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
- // 고정 길이 직접 매핑 (charLength만큼씩 읽음)
260
- for (let i = 0; i < input.length; i += this.charLength) {
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
- dduBinary += this.getBinaryFromIndex(charIndex);
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
- const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
269
- return decoded.toString(this.encoding);
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.6",
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": "NODE_OPTIONS='--expose-gc' tsx ./src/test/test-comprehensive.ts",
11
- "test:no-gc": "tsx ./src/test/test-comprehensive.ts",
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": {