@ddunigma/node 1.1.6 → 1.1.7

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 CHANGED
@@ -64,6 +64,7 @@ const encoder4 = new Ddu64(undefined, undefined, {
64
64
  const text = "안녕하세요";
65
65
  const encoded = encoder1.encode(text);
66
66
  const decoded = encoder1.decode(encoded);
67
+ const decodedBuffer = encoder1.decodeToBuffer(encoded);
67
68
  ```
68
69
 
69
70
  ### 커스텀 Charset 사용
@@ -82,4 +83,47 @@ const encoder = new Ddu64(koreanChars, "뭐");
82
83
  const text = "안녕하세요12";
83
84
  const encoded = encoder.encode(text);
84
85
  const decoded = encoder.decode(encoded);
86
+ const decodedBuffer = encoder.decodeToBuffer(encoded);
87
+ ```
88
+
89
+ ## API
90
+
91
+ ### `new Ddu64(dduChar?, paddingChar?, options?)`
92
+
93
+ 인코더 인스턴스를 생성합니다.
94
+
95
+ **Parameters:**
96
+ - `dduChar` (string | string[]): charset 문자열 또는 배열
97
+ - `paddingChar` (string): 패딩 문자
98
+ - `options` (DduOptions): 옵션 객체
99
+ - `dduSetSymbol`: 미리 정의된 charset 사용
100
+ - `encoding`: Buffer encoding (기본값: 'utf-8')
101
+ - `usePowerOfTwo`: 2의 제곱수 강제 여부
102
+ - `useBuildErrorReturn`: 에러 발생 시 throw 여부
103
+
104
+ ### `encode(data: string | Buffer): string`
105
+
106
+ 데이터를 인코딩
107
+
108
+ ### `decode(encoded: string): string`
109
+
110
+ 인코딩된 문자열을 디코딩
111
+
112
+ ### `decodeToBuffer(encoded: string): Buffer`
113
+
114
+ 인코딩된 문자열을 Buffer로 직접 디코딩
115
+
116
+ ### `getCharSetInfo()`
117
+
118
+ 현재 charset 정보를 반환
119
+
120
+ ```typescript
121
+ {
122
+ charSet: string[];
123
+ paddingChar: string;
124
+ charLength: number;
125
+ bitLength: number;
126
+ usePowerOfTwo: boolean;
127
+ encoding: BufferEncoding;
128
+ }
85
129
  ```
@@ -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
  }
@@ -4,46 +4,25 @@ exports.BaseDdu = void 0;
4
4
  class BaseDdu {
5
5
  constructor() {
6
6
  this.defaultEncoding = "utf-8";
7
- /**
8
- * 이진수 변환 룩업 테이블 (0-255 -> 8비트 이진 문자열)
9
- */
10
7
  this.binaryLookup = Array.from({ length: 256 }, (_, i) => i.toString(2).padStart(8, "0"));
11
8
  }
12
- /**
13
- * 정규표현식 특수문자 이스케이프
14
- */
15
9
  escapeRegExp(str) {
16
10
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
17
11
  }
18
- /**
19
- * 문자열을 지정된 길이로 분할하는 제너레이터
20
- */
21
12
  *splitString(s, length) {
22
13
  for (let i = 0; i < s.length; i += length) {
23
14
  yield s.slice(i, Math.min(i + length, s.length));
24
15
  }
25
16
  }
26
- /**
27
- * 2의 거듭제곱 길이 계산
28
- */
29
17
  getLargestPowerOfTwo(n) {
30
18
  return Math.pow(2, Math.floor(Math.log2(n)));
31
19
  }
32
- /**
33
- * 2의 거듭제곱 지수 계산
34
- */
35
20
  getLargestPowerOfTwoExponent(n) {
36
21
  return Math.floor(Math.log2(n));
37
22
  }
38
- /**
39
- * 비트 길이 계산
40
- */
41
23
  getBitLength(setLength) {
42
24
  return Math.ceil(Math.log2(setLength));
43
25
  }
44
- /**
45
- * Buffer를 DDU Binary 배열로 변환
46
- */
47
26
  bufferToDduBinary(input, bitLength) {
48
27
  // 성능 최적화: reduce 대신 for loop + join 사용
49
28
  if (input.length === 0) {
@@ -61,9 +40,6 @@ class BaseDdu {
61
40
  }
62
41
  return { dduBinary, padding };
63
42
  }
64
- /**
65
- * DDU Binary를 Buffer로 변환
66
- */
67
43
  dduBinaryToBuffer(decodedBin, paddingBits) {
68
44
  if (paddingBits > 0) {
69
45
  decodedBin = decodedBin.slice(0, -paddingBits);
@@ -10,6 +10,7 @@ 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);
@@ -17,5 +18,18 @@ export declare class Ddu64 extends BaseDdu {
17
18
  private getBinaryFromIndex;
18
19
  private validateCombinationDuplicates;
19
20
  encode(input: Buffer | string, _options?: DduOptions): string;
21
+ decodeToBuffer(input: string, _options?: DduOptions): Buffer;
20
22
  decode(input: string, _options?: DduOptions): string;
23
+ /**
24
+ * 테스트 및 디버깅용 getter 메서드
25
+ * 인코더의 내부 상태 정보를 반환
26
+ */
27
+ getCharSetInfo(): {
28
+ charSet: string[];
29
+ paddingChar: string;
30
+ charLength: number;
31
+ bitLength: number;
32
+ usePowerOfTwo: boolean;
33
+ encoding: BufferEncoding;
34
+ };
21
35
  }
@@ -23,12 +23,23 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
23
23
  const finalPadding = paddingChar !== null && paddingChar !== void 0 ? paddingChar : dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.paddingChar;
24
24
  if (finalDduChar) {
25
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;
26
+ if (!finalPadding) {
27
+ throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
28
+ }
29
+ // 문자열을 배열로 변환 (중복 제거 없이)
30
+ const arr = typeof finalDduChar === "string" ? [...finalDduChar.trim()] : finalDduChar;
31
+ // 중복 검사 (useBuildErrorReturn이 true일 때만)
32
+ if (shouldThrowError) {
33
+ const uniqueChars = new Set(arr);
34
+ if (uniqueChars.size !== arr.length) {
35
+ const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
36
+ throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`);
37
+ }
38
+ }
29
39
  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}`);
40
+ if (arr.length < len) {
41
+ throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
42
+ }
32
43
  const usePow2 = (dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === true || ((dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.usePowerOfTwo) === undefined && len > 0 && (len & (len - 1)) === 0);
33
44
  if (usePow2) {
34
45
  const exp = this.getLargestPowerOfTwoExponent(len);
@@ -73,22 +84,25 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
73
84
  this.dduChar = normalized.charSet;
74
85
  this.paddingChar = normalized.padding;
75
86
  this.charLength = normalized.charLength;
76
- this.bitLength = normalized.bitLength;
77
87
  this.isPredefinedCharSet = normalized.isPredefined;
78
88
  this.encoding = (_g = dduOptions === null || dduOptions === void 0 ? void 0 : dduOptions.encoding) !== null && _g !== void 0 ? _g : this.defaultEncoding;
79
89
  const dduLength = this.dduChar.length;
90
+ const recalculatedBitLength = this.getBitLength(dduLength);
80
91
  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);
92
+ this.bitLength = this.usePowerOfTwo
93
+ ? this.getLargestPowerOfTwoExponent(dduLength)
94
+ : recalculatedBitLength;
95
+ this.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : recalculatedBitLength;
96
+ this.maxBinaryValue = Math.pow(2, this.effectiveBitLength);
97
+ // 성능 최적화: 단일 구현 사용 (벤치마크 결과 Method2가 더 빠르고 안정적)
98
+ // Method2는 32비트 이상에서도 오버플로우 없이 정확한 결과 제공
99
+ this.binaryChunkToIntFn = (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
85
100
  this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
86
- if (this.charLength >= 2 && !this.isPredefinedCharSet) {
101
+ if (this.charLength === 1 && !this.isPredefinedCharSet) {
87
102
  this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
88
103
  }
89
104
  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"));
105
+ this.indexToBinaryCache = Array.from({ length: this.maxBinaryValue }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
92
106
  }
93
107
  else {
94
108
  this.indexToBinaryCache = null;
@@ -114,50 +128,67 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
114
128
  }
115
129
  return false;
116
130
  };
117
- // 중복 제거 (커스텀 charset)
118
- if (!isPredefined) {
119
- const uniqueChars = new Set(charSet);
120
- if (uniqueChars.size !== charSet.length) {
121
- if (shouldThrowError)
122
- throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
123
- charSet = Array.from(uniqueChars);
131
+ // 중복 제거 (모든 charset에 적용)
132
+ const uniqueChars = new Set(charSet);
133
+ if (uniqueChars.size !== charSet.length) {
134
+ const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
135
+ const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`;
136
+ if (shouldThrowError)
137
+ throw new Error(errorMsg);
138
+ // 중복 제거 후 계속 진행
139
+ charSet = Array.from(uniqueChars);
140
+ if (!isPredefined) {
124
141
  requiredLength = charSet.length;
125
142
  }
126
143
  }
127
144
  // 검증
128
- if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
145
+ if (validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters. Required: ${requiredLength}, Provided: ${charSet.length}`)) {
146
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
147
+ }
148
+ if (validate(requiredLength < 2, `[Ddu64 normalizeCharSet] At least 2 unique characters are required. Provided: ${requiredLength}`)) {
149
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
150
+ }
151
+ if (validate(bitLength <= 0, `[Ddu64 normalizeCharSet] Invalid bit length (${bitLength}) for charset size ${requiredLength}`)) {
129
152
  return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
130
153
  }
131
154
  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}`);
155
+ validate(charLength === 0, `[Ddu64 normalizeCharSet] Empty charset. Required: ${requiredLength} characters`);
133
156
  const invalidIndex = charSet.findIndex(char => char.length !== charLength);
134
157
  if (invalidIndex !== -1) {
135
- if (shouldThrowError)
136
- throw new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
158
+ if (shouldThrowError) {
159
+ throw new Error(`[Ddu64 normalizeCharSet] Inconsistent character length. Expected: ${charLength}, but character at index ${invalidIndex} ("${charSet[invalidIndex]}") has length ${charSet[invalidIndex].length}`);
160
+ }
137
161
  charSet = charSet.filter(char => char.length === charLength);
138
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
162
+ validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after filtering. Required: ${requiredLength}, Remaining: ${charSet.length}`);
139
163
  charLength = (_d = (_c = charSet[0]) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0;
140
164
  }
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}`);
165
+ validate(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after validation`);
166
+ validate(padding.length !== charLength, `[Ddu64 normalizeCharSet] Padding character length mismatch. Expected: ${charLength}, Got: ${padding.length} (padding: "${padding}")`);
143
167
  if (new Set(charSet).has(padding)) {
144
- if (shouldThrowError)
145
- throw new Error(`Padding character "${padding}" cannot be in the character set`);
168
+ if (shouldThrowError) {
169
+ throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
170
+ }
146
171
  charSet = charSet.filter(char => char !== padding);
147
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
172
+ validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`);
148
173
  charLength = (_f = (_e = charSet[0]) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : 0;
149
174
  }
150
175
  return { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
151
176
  }
152
177
  getBinaryFromIndex(index) {
153
178
  var _a, _b;
179
+ if (index < 0 || index >= this.maxBinaryValue) {
180
+ throw new Error(`[Ddu64] Binary index overflow. Received: ${index}, Allowed range: 0-${this.maxBinaryValue - 1}`);
181
+ }
154
182
  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
183
  }
156
184
  validateCombinationDuplicates(charSet, paddingChar, requiredLength) {
157
185
  const charLength = charSet[0].length;
186
+ // 조합 충돌 검사는 단일 문자 집합이면서 비교적 작은 경우(<=256)에만 적용한다.
187
+ if (charLength !== 1 || requiredLength > 256) {
188
+ return;
189
+ }
158
190
  const allStrings = new Set([...charSet.slice(0, requiredLength), paddingChar]);
159
191
  const limit = Math.min(charSet.length, requiredLength);
160
- // 2개 조합 검사
161
192
  for (let i = 0; i < limit; i++) {
162
193
  for (let j = 0; j < limit; j++) {
163
194
  const combo = charSet[i] + charSet[j];
@@ -167,32 +198,17 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
167
198
  }
168
199
  const charPad = charSet[i] + paddingChar;
169
200
  const padChar = paddingChar + charSet[i];
170
- if (allStrings.has(charPad))
201
+ if (allStrings.has(charPad)) {
171
202
  throw new Error(`Combination conflict: "${charSet[i]}" + padding "${paddingChar}" = "${charPad}"`);
172
- if (allStrings.has(padChar))
203
+ }
204
+ if (allStrings.has(padChar)) {
173
205
  throw new Error(`Combination conflict: padding "${paddingChar}" + "${charSet[i]}" = "${padChar}"`);
206
+ }
174
207
  }
175
- // 패딩 + 패딩 조합 검사
176
208
  const doublePad = paddingChar + paddingChar;
177
209
  if (allStrings.has(doublePad)) {
178
210
  throw new Error(`Combination conflict: double padding "${doublePad}" already exists`);
179
211
  }
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
212
  }
197
213
  encode(input, _options) {
198
214
  // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
@@ -207,8 +223,8 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
207
223
  for (let i = 0; i < dduBinary.length; i++) {
208
224
  const binaryChunk = dduBinary[i];
209
225
  const charInt = this.binaryChunkToIntFn(binaryChunk);
210
- if (this.charLength === 1 && !this.usePowerOfTwo) {
211
- // 가변 길이 조합 인코딩
226
+ if (!this.usePowerOfTwo) {
227
+ // 가변 길이 조합 인코딩 (멀티바이트 문자도 지원)
212
228
  const quotient = Math.floor(charInt / dduLength);
213
229
  const remainder = charInt % dduLength;
214
230
  resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
@@ -225,45 +241,78 @@ class Ddu64 extends BaseDdu_1.BaseDdu {
225
241
  }
226
242
  return resultString;
227
243
  }
228
- decode(input, _options) {
229
- // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
230
- // 패딩 정보 추출
244
+ decodeToBuffer(input, _options) {
231
245
  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);
246
+ if (input.length >= this.paddingChar.length) {
247
+ const padCharIndex = input.lastIndexOf(this.paddingChar);
248
+ if (padCharIndex >= 0 &&
249
+ padCharIndex % this.charLength === 0 &&
250
+ padCharIndex + this.paddingChar.length <= input.length) {
251
+ const paddingSection = input.slice(padCharIndex + this.paddingChar.length);
252
+ if (paddingSection.length === 0) {
253
+ throw new Error(`[Ddu64 decode] Invalid padding format. Missing padding length after "${this.paddingChar}"`);
254
+ }
255
+ paddingBits = parseInt(paddingSection, 10);
256
+ if (isNaN(paddingBits) ||
257
+ paddingSection !== paddingBits.toString() ||
258
+ paddingBits < 0 ||
259
+ paddingBits >= this.effectiveBitLength) {
260
+ throw new Error(`[Ddu64 decode] Invalid padding format. Expected integer between 0 and ${this.effectiveBitLength - 1}, Got: "${paddingSection}"`);
261
+ }
262
+ input = input.substring(0, padCharIndex);
263
+ }
237
264
  }
238
265
  let dduBinary = "";
239
- // 생성자에서 설정된 값 사용
240
266
  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];
267
+ if (!this.usePowerOfTwo) {
268
+ const chunkSize = this.charLength * 2;
269
+ for (let i = 0; i < input.length; i += chunkSize) {
270
+ const firstChar = input.slice(i, i + this.charLength);
271
+ const secondChar = input.slice(i + this.charLength, i + chunkSize);
247
272
  const firstIndex = this.dduBinaryLookup.get(firstChar);
248
273
  const secondIndex = this.dduBinaryLookup.get(secondChar);
249
- if (firstIndex === undefined || secondIndex === undefined)
250
- throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
274
+ if (firstIndex === undefined || secondIndex === undefined) {
275
+ const invalidChar = firstIndex === undefined ? firstChar : secondChar;
276
+ throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${invalidChar}", Position: ${i}, Expected charset size: ${dduLength}`);
277
+ }
251
278
  const value = firstIndex * dduLength + secondIndex;
279
+ if (value >= this.maxBinaryValue) {
280
+ throw new Error(`[Ddu64 decode] Invalid character combination detected. Calculated value ${value} exceeds binary range ${this.maxBinaryValue - 1}.`);
281
+ }
252
282
  dduBinary += this.getBinaryFromIndex(value);
253
283
  }
254
284
  }
255
285
  else {
256
- // 고정 길이 직접 매핑 (charLength만큼씩 읽음)
257
286
  for (let i = 0; i < input.length; i += this.charLength) {
258
287
  const charChunk = input.slice(i, i + this.charLength);
259
288
  const charIndex = this.dduBinaryLookup.get(charChunk);
260
- if (charIndex === undefined)
261
- throw new Error(`Invalid character: ${charChunk}`);
289
+ if (charIndex === undefined) {
290
+ throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${this.charLength}`);
291
+ }
292
+ if (charIndex >= this.maxBinaryValue) {
293
+ throw new Error(`[Ddu64 decode] Invalid binary index ${charIndex}. Allowed range: 0-${this.maxBinaryValue - 1}`);
294
+ }
262
295
  dduBinary += this.getBinaryFromIndex(charIndex);
263
296
  }
264
297
  }
265
- const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
266
- return decoded.toString(this.encoding);
298
+ return this.dduBinaryToBuffer(dduBinary, paddingBits);
299
+ }
300
+ decode(input, _options) {
301
+ return this.decodeToBuffer(input, _options).toString(this.encoding);
302
+ }
303
+ /**
304
+ * 테스트 및 디버깅용 getter 메서드
305
+ * 인코더의 내부 상태 정보를 반환
306
+ */
307
+ getCharSetInfo() {
308
+ return {
309
+ charSet: [...this.dduChar],
310
+ paddingChar: this.paddingChar,
311
+ charLength: this.charLength,
312
+ bitLength: this.bitLength,
313
+ usePowerOfTwo: this.usePowerOfTwo,
314
+ encoding: this.encoding,
315
+ };
267
316
  }
268
317
  }
269
318
  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);
@@ -10,6 +10,7 @@ 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);
@@ -17,5 +18,18 @@ export declare class Ddu64 extends BaseDdu {
17
18
  private getBinaryFromIndex;
18
19
  private validateCombinationDuplicates;
19
20
  encode(input: Buffer | string, _options?: DduOptions): string;
21
+ decodeToBuffer(input: string, _options?: DduOptions): Buffer;
20
22
  decode(input: string, _options?: DduOptions): string;
23
+ /**
24
+ * 테스트 및 디버깅용 getter 메서드
25
+ * 인코더의 내부 상태 정보를 반환
26
+ */
27
+ getCharSetInfo(): {
28
+ charSet: string[];
29
+ paddingChar: string;
30
+ charLength: number;
31
+ bitLength: number;
32
+ usePowerOfTwo: boolean;
33
+ encoding: BufferEncoding;
34
+ };
21
35
  }
@@ -11,6 +11,7 @@ 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) {
@@ -29,12 +30,23 @@ export class Ddu64 extends BaseDdu {
29
30
  const finalPadding = paddingChar ?? dduOptions?.paddingChar;
30
31
  if (finalDduChar) {
31
32
  // 커스텀 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;
33
+ if (!finalPadding) {
34
+ throw new Error(`[Ddu64 Constructor] paddingChar is required when dduChar is provided. Received: dduChar=${typeof finalDduChar}, paddingChar=${finalPadding}`);
35
+ }
36
+ // 문자열을 배열로 변환 (중복 제거 없이)
37
+ const arr = typeof finalDduChar === "string" ? [...finalDduChar.trim()] : finalDduChar;
38
+ // 중복 검사 (useBuildErrorReturn이 true일 때만)
39
+ if (shouldThrowError) {
40
+ const uniqueChars = new Set(arr);
41
+ if (uniqueChars.size !== arr.length) {
42
+ const duplicates = arr.filter((char, index) => arr.indexOf(char) !== index);
43
+ throw new Error(`[Ddu64 Constructor] Character set contains duplicate characters. Total: ${arr.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`);
44
+ }
45
+ }
35
46
  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}`);
47
+ if (arr.length < len) {
48
+ throw new Error(`[Ddu64 Constructor] Insufficient characters in charset. Required: ${len}, Provided: ${arr.length}`);
49
+ }
38
50
  const usePow2 = dduOptions?.usePowerOfTwo === true || (dduOptions?.usePowerOfTwo === undefined && len > 0 && (len & (len - 1)) === 0);
39
51
  if (usePow2) {
40
52
  const exp = this.getLargestPowerOfTwoExponent(len);
@@ -79,22 +91,25 @@ export class Ddu64 extends BaseDdu {
79
91
  this.dduChar = normalized.charSet;
80
92
  this.paddingChar = normalized.padding;
81
93
  this.charLength = normalized.charLength;
82
- this.bitLength = normalized.bitLength;
83
94
  this.isPredefinedCharSet = normalized.isPredefined;
84
95
  this.encoding = dduOptions?.encoding ?? this.defaultEncoding;
85
96
  const dduLength = this.dduChar.length;
97
+ const recalculatedBitLength = this.getBitLength(dduLength);
86
98
  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);
99
+ this.bitLength = this.usePowerOfTwo
100
+ ? this.getLargestPowerOfTwoExponent(dduLength)
101
+ : recalculatedBitLength;
102
+ this.effectiveBitLength = this.usePowerOfTwo ? this.bitLength : recalculatedBitLength;
103
+ this.maxBinaryValue = 2 ** this.effectiveBitLength;
104
+ // 성능 최적화: 단일 구현 사용 (벤치마크 결과 Method2가 더 빠르고 안정적)
105
+ // Method2는 32비트 이상에서도 오버플로우 없이 정확한 결과 제공
106
+ this.binaryChunkToIntFn = (chunk) => chunk.split('').reduce((v, c) => v * 2 + (c.charCodeAt(0) & 1), 0);
91
107
  this.dduChar.forEach((char, index) => this.dduBinaryLookup.set(char, index));
92
- if (this.charLength >= 2 && !this.isPredefinedCharSet) {
108
+ if (this.charLength === 1 && !this.isPredefinedCharSet) {
93
109
  this.validateCombinationDuplicates(this.dduChar, this.paddingChar, dduLength);
94
110
  }
95
111
  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"));
112
+ this.indexToBinaryCache = Array.from({ length: this.maxBinaryValue }, (_, i) => i.toString(2).padStart(this.effectiveBitLength, "0"));
98
113
  }
99
114
  else {
100
115
  this.indexToBinaryCache = null;
@@ -118,49 +133,66 @@ export class Ddu64 extends BaseDdu {
118
133
  }
119
134
  return false;
120
135
  };
121
- // 중복 제거 (커스텀 charset)
122
- if (!isPredefined) {
123
- const uniqueChars = new Set(charSet);
124
- if (uniqueChars.size !== charSet.length) {
125
- if (shouldThrowError)
126
- throw new Error(`Character set contains duplicate characters. Unique: ${uniqueChars.size}, Total: ${charSet.length}`);
127
- charSet = Array.from(uniqueChars);
136
+ // 중복 제거 (모든 charset에 적용)
137
+ const uniqueChars = new Set(charSet);
138
+ if (uniqueChars.size !== charSet.length) {
139
+ const duplicates = charSet.filter((char, index) => charSet.indexOf(char) !== index);
140
+ const errorMsg = `[Ddu64 normalizeCharSet] Character set contains duplicate characters. Total: ${charSet.length}, Unique: ${uniqueChars.size}, Duplicates: [${[...new Set(duplicates)].join(', ')}]`;
141
+ if (shouldThrowError)
142
+ throw new Error(errorMsg);
143
+ // 중복 제거 후 계속 진행
144
+ charSet = Array.from(uniqueChars);
145
+ if (!isPredefined) {
128
146
  requiredLength = charSet.length;
129
147
  }
130
148
  }
131
149
  // 검증
132
- if (validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`)) {
150
+ if (validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters. Required: ${requiredLength}, Provided: ${charSet.length}`)) {
151
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
152
+ }
153
+ if (validate(requiredLength < 2, `[Ddu64 normalizeCharSet] At least 2 unique characters are required. Provided: ${requiredLength}`)) {
154
+ return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
155
+ }
156
+ if (validate(bitLength <= 0, `[Ddu64 normalizeCharSet] Invalid bit length (${bitLength}) for charset size ${requiredLength}`)) {
133
157
  return this.normalizeCharSet(charSet, padding, requiredLength, bitLength, isPredefined, shouldThrowError, dduOptions);
134
158
  }
135
159
  let charLength = charSet[0]?.length ?? 0;
136
- validate(charLength === 0, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
160
+ validate(charLength === 0, `[Ddu64 normalizeCharSet] Empty charset. Required: ${requiredLength} characters`);
137
161
  const invalidIndex = charSet.findIndex(char => char.length !== charLength);
138
162
  if (invalidIndex !== -1) {
139
- if (shouldThrowError)
140
- throw new Error(`All characters must have the same length. Expected: ${charLength}, but index ${invalidIndex} has length ${charSet[invalidIndex].length}`);
163
+ if (shouldThrowError) {
164
+ throw new Error(`[Ddu64 normalizeCharSet] Inconsistent character length. Expected: ${charLength}, but character at index ${invalidIndex} ("${charSet[invalidIndex]}") has length ${charSet[invalidIndex].length}`);
165
+ }
141
166
  charSet = charSet.filter(char => char.length === charLength);
142
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
167
+ validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after filtering. Required: ${requiredLength}, Remaining: ${charSet.length}`);
143
168
  charLength = charSet[0]?.length ?? 0;
144
169
  }
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}`);
170
+ validate(!charSet.length, `[Ddu64 normalizeCharSet] Empty charset after validation`);
171
+ validate(padding.length !== charLength, `[Ddu64 normalizeCharSet] Padding character length mismatch. Expected: ${charLength}, Got: ${padding.length} (padding: "${padding}")`);
147
172
  if (new Set(charSet).has(padding)) {
148
- if (shouldThrowError)
149
- throw new Error(`Padding character "${padding}" cannot be in the character set`);
173
+ if (shouldThrowError) {
174
+ throw new Error(`[Ddu64 normalizeCharSet] Padding character "${padding}" conflicts with charset. Padding must not be in the character set.`);
175
+ }
150
176
  charSet = charSet.filter(char => char !== padding);
151
- validate(charSet.length < requiredLength, `${this.constructor.name} requires at least ${requiredLength} characters. Provided: ${charSet.length}`);
177
+ validate(charSet.length < requiredLength, `[Ddu64 normalizeCharSet] Insufficient characters after removing padding conflict. Required: ${requiredLength}, Remaining: ${charSet.length}`);
152
178
  charLength = charSet[0]?.length ?? 0;
153
179
  }
154
180
  return { charSet: charSet.slice(0, requiredLength), padding, requiredLength, bitLength, isPredefined, charLength };
155
181
  }
156
182
  getBinaryFromIndex(index) {
183
+ if (index < 0 || index >= this.maxBinaryValue) {
184
+ throw new Error(`[Ddu64] Binary index overflow. Received: ${index}, Allowed range: 0-${this.maxBinaryValue - 1}`);
185
+ }
157
186
  return this.indexToBinaryCache?.[index] ?? index.toString(2).padStart(this.effectiveBitLength, "0");
158
187
  }
159
188
  validateCombinationDuplicates(charSet, paddingChar, requiredLength) {
160
189
  const charLength = charSet[0].length;
190
+ // 조합 충돌 검사는 단일 문자 집합이면서 비교적 작은 경우(<=256)에만 적용한다.
191
+ if (charLength !== 1 || requiredLength > 256) {
192
+ return;
193
+ }
161
194
  const allStrings = new Set([...charSet.slice(0, requiredLength), paddingChar]);
162
195
  const limit = Math.min(charSet.length, requiredLength);
163
- // 2개 조합 검사
164
196
  for (let i = 0; i < limit; i++) {
165
197
  for (let j = 0; j < limit; j++) {
166
198
  const combo = charSet[i] + charSet[j];
@@ -170,32 +202,17 @@ export class Ddu64 extends BaseDdu {
170
202
  }
171
203
  const charPad = charSet[i] + paddingChar;
172
204
  const padChar = paddingChar + charSet[i];
173
- if (allStrings.has(charPad))
205
+ if (allStrings.has(charPad)) {
174
206
  throw new Error(`Combination conflict: "${charSet[i]}" + padding "${paddingChar}" = "${charPad}"`);
175
- if (allStrings.has(padChar))
207
+ }
208
+ if (allStrings.has(padChar)) {
176
209
  throw new Error(`Combination conflict: padding "${paddingChar}" + "${charSet[i]}" = "${padChar}"`);
210
+ }
177
211
  }
178
- // 패딩 + 패딩 조합 검사
179
212
  const doublePad = paddingChar + paddingChar;
180
213
  if (allStrings.has(doublePad)) {
181
214
  throw new Error(`Combination conflict: double padding "${doublePad}" already exists`);
182
215
  }
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
216
  }
200
217
  encode(input, _options) {
201
218
  // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
@@ -210,8 +227,8 @@ export class Ddu64 extends BaseDdu {
210
227
  for (let i = 0; i < dduBinary.length; i++) {
211
228
  const binaryChunk = dduBinary[i];
212
229
  const charInt = this.binaryChunkToIntFn(binaryChunk);
213
- if (this.charLength === 1 && !this.usePowerOfTwo) {
214
- // 가변 길이 조합 인코딩
230
+ if (!this.usePowerOfTwo) {
231
+ // 가변 길이 조합 인코딩 (멀티바이트 문자도 지원)
215
232
  const quotient = Math.floor(charInt / dduLength);
216
233
  const remainder = charInt % dduLength;
217
234
  resultParts[i] = this.dduChar[quotient] + this.dduChar[remainder];
@@ -228,44 +245,77 @@ export class Ddu64 extends BaseDdu {
228
245
  }
229
246
  return resultString;
230
247
  }
231
- decode(input, _options) {
232
- // options는 구버전 호환성을 위해 유지하지만 사용하지 않음
233
- // 패딩 정보 추출
248
+ decodeToBuffer(input, _options) {
234
249
  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);
250
+ if (input.length >= this.paddingChar.length) {
251
+ const padCharIndex = input.lastIndexOf(this.paddingChar);
252
+ if (padCharIndex >= 0 &&
253
+ padCharIndex % this.charLength === 0 &&
254
+ padCharIndex + this.paddingChar.length <= input.length) {
255
+ const paddingSection = input.slice(padCharIndex + this.paddingChar.length);
256
+ if (paddingSection.length === 0) {
257
+ throw new Error(`[Ddu64 decode] Invalid padding format. Missing padding length after "${this.paddingChar}"`);
258
+ }
259
+ paddingBits = parseInt(paddingSection, 10);
260
+ if (isNaN(paddingBits) ||
261
+ paddingSection !== paddingBits.toString() ||
262
+ paddingBits < 0 ||
263
+ paddingBits >= this.effectiveBitLength) {
264
+ throw new Error(`[Ddu64 decode] Invalid padding format. Expected integer between 0 and ${this.effectiveBitLength - 1}, Got: "${paddingSection}"`);
265
+ }
266
+ input = input.substring(0, padCharIndex);
267
+ }
240
268
  }
241
269
  let dduBinary = "";
242
- // 생성자에서 설정된 값 사용
243
270
  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];
271
+ if (!this.usePowerOfTwo) {
272
+ const chunkSize = this.charLength * 2;
273
+ for (let i = 0; i < input.length; i += chunkSize) {
274
+ const firstChar = input.slice(i, i + this.charLength);
275
+ const secondChar = input.slice(i + this.charLength, i + chunkSize);
250
276
  const firstIndex = this.dduBinaryLookup.get(firstChar);
251
277
  const secondIndex = this.dduBinaryLookup.get(secondChar);
252
- if (firstIndex === undefined || secondIndex === undefined)
253
- throw new Error(`Invalid character: ${firstChar} or ${secondChar}`);
278
+ if (firstIndex === undefined || secondIndex === undefined) {
279
+ const invalidChar = firstIndex === undefined ? firstChar : secondChar;
280
+ throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${invalidChar}", Position: ${i}, Expected charset size: ${dduLength}`);
281
+ }
254
282
  const value = firstIndex * dduLength + secondIndex;
283
+ if (value >= this.maxBinaryValue) {
284
+ throw new Error(`[Ddu64 decode] Invalid character combination detected. Calculated value ${value} exceeds binary range ${this.maxBinaryValue - 1}.`);
285
+ }
255
286
  dduBinary += this.getBinaryFromIndex(value);
256
287
  }
257
288
  }
258
289
  else {
259
- // 고정 길이 직접 매핑 (charLength만큼씩 읽음)
260
290
  for (let i = 0; i < input.length; i += this.charLength) {
261
291
  const charChunk = input.slice(i, i + this.charLength);
262
292
  const charIndex = this.dduBinaryLookup.get(charChunk);
263
- if (charIndex === undefined)
264
- throw new Error(`Invalid character: ${charChunk}`);
293
+ if (charIndex === undefined) {
294
+ throw new Error(`[Ddu64 decode] Invalid character in encoded string. Character: "${charChunk}", Position: ${i}, Charset size: ${dduLength}, Character length: ${this.charLength}`);
295
+ }
296
+ if (charIndex >= this.maxBinaryValue) {
297
+ throw new Error(`[Ddu64 decode] Invalid binary index ${charIndex}. Allowed range: 0-${this.maxBinaryValue - 1}`);
298
+ }
265
299
  dduBinary += this.getBinaryFromIndex(charIndex);
266
300
  }
267
301
  }
268
- const decoded = this.dduBinaryToBuffer(dduBinary, paddingBits);
269
- return decoded.toString(this.encoding);
302
+ return this.dduBinaryToBuffer(dduBinary, paddingBits);
303
+ }
304
+ decode(input, _options) {
305
+ return this.decodeToBuffer(input, _options).toString(this.encoding);
306
+ }
307
+ /**
308
+ * 테스트 및 디버깅용 getter 메서드
309
+ * 인코더의 내부 상태 정보를 반환
310
+ */
311
+ getCharSetInfo() {
312
+ return {
313
+ charSet: [...this.dduChar],
314
+ paddingChar: this.paddingChar,
315
+ charLength: this.charLength,
316
+ bitLength: this.bitLength,
317
+ usePowerOfTwo: this.usePowerOfTwo,
318
+ encoding: this.encoding,
319
+ };
270
320
  }
271
321
  }
package/package.json CHANGED
@@ -1,14 +1,15 @@
1
1
  {
2
2
  "name": "@ddunigma/node",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
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": {