@cloudpss/ubjson 0.4.32 → 0.4.33

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.
Files changed (56) hide show
  1. package/dist/common/constants.d.ts +22 -20
  2. package/dist/common/constants.js +1 -21
  3. package/dist/common/constants.js.map +1 -1
  4. package/dist/common/decoder.d.ts +4 -20
  5. package/dist/common/decoder.js +217 -178
  6. package/dist/common/decoder.js.map +1 -1
  7. package/dist/common/encoder.d.ts +4 -52
  8. package/dist/common/encoder.js +215 -259
  9. package/dist/common/encoder.js.map +1 -1
  10. package/dist/common/string-decoder.d.ts +3 -8
  11. package/dist/common/string-decoder.js +190 -46
  12. package/dist/common/string-decoder.js.map +1 -1
  13. package/dist/common/string-encoder.d.ts +2 -8
  14. package/dist/common/string-encoder.js +4 -44
  15. package/dist/common/string-encoder.js.map +1 -1
  16. package/dist/encoder.js +2 -21
  17. package/dist/encoder.js.map +1 -1
  18. package/dist/index.d.ts +1 -2
  19. package/dist/index.js +2 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/rxjs/decoder.js +2 -2
  22. package/dist/rxjs/decoder.js.map +1 -1
  23. package/dist/rxjs/index.d.ts +1 -0
  24. package/dist/rxjs/index.js +1 -0
  25. package/dist/rxjs/index.js.map +1 -1
  26. package/dist/stream/index.d.ts +1 -0
  27. package/dist/stream/index.js +2 -1
  28. package/dist/stream/index.js.map +1 -1
  29. package/dist/stream-helper/decoder.d.ts +1 -1
  30. package/dist/stream-helper/decoder.js +1 -1
  31. package/dist/stream-helper/decoder.js.map +1 -1
  32. package/dist/utils.d.ts +5 -1
  33. package/dist/utils.js +13 -4
  34. package/dist/utils.js.map +1 -1
  35. package/package.json +3 -2
  36. package/src/common/constants.ts +22 -21
  37. package/src/common/decoder.ts +197 -162
  38. package/src/common/encoder.ts +200 -275
  39. package/src/common/string-decoder.ts +173 -41
  40. package/src/common/string-encoder.ts +6 -41
  41. package/src/encoder.ts +2 -16
  42. package/src/index.ts +2 -5
  43. package/src/rxjs/decoder.ts +2 -2
  44. package/src/rxjs/index.ts +1 -0
  45. package/src/stream/index.ts +2 -0
  46. package/src/stream-helper/decoder.ts +1 -1
  47. package/src/utils.ts +14 -4
  48. package/tests/decode.js +69 -5
  49. package/tests/e2e.js +12 -1
  50. package/tests/encode.js +33 -16
  51. package/tests/rxjs/decode.js +3 -2
  52. package/tests/rxjs/encode.js +2 -3
  53. package/tests/stream/decode.js +2 -1
  54. package/tests/stream/encode.js +2 -3
  55. package/tests/string-encoding.js +77 -25
  56. package/tsconfig.json +3 -1
@@ -1,3 +1,5 @@
1
+ import { UnexpectedEof } from '../utils.js';
2
+
1
3
  /* c8 ignore next 2: TextDecoder always present, fallback tested */
2
4
  export const textDecoder =
3
5
  typeof TextDecoder == 'function' ? new TextDecoder('utf8', { ignoreBOM: true, fatal: false }) : null;
@@ -8,6 +10,8 @@ export const TEXT_ENCODER_THRESHOLD = textDecoder == null ? 0xffff_ffff : 200;
8
10
  const CHUNK_SIZE = 0x1000;
9
11
  const REPLACE_CHAR = 0xfffd;
10
12
 
13
+ const fromCharCode = String.fromCharCode;
14
+
11
15
  /** 解码 */
12
16
  export function decodeJs(bytes: Uint8Array, begin: number, end: number): string {
13
17
  let offset = begin;
@@ -45,67 +49,195 @@ export function decodeJs(bytes: Uint8Array, begin: number, end: number): string
45
49
  }
46
50
 
47
51
  if (units.length >= CHUNK_SIZE) {
48
- result += String.fromCharCode(...units);
52
+ result += fromCharCode(...units);
49
53
  units.length = 0;
50
54
  }
51
55
  }
52
56
 
53
57
  if (units.length > 0) {
54
- result += String.fromCharCode(...units);
58
+ result += fromCharCode(...units);
55
59
  }
56
60
 
57
61
  return result;
58
62
  }
59
63
 
60
- /** 解码 */
64
+ /** 解码 Ascii */
65
+ function longStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
66
+ const bytes = Array.from<number>({ length });
67
+ for (let i = 0; i < length; i++) {
68
+ const byte = buf[begin++];
69
+ if ((byte & 0x80) > 0) {
70
+ return;
71
+ }
72
+ bytes[i] = byte;
73
+ }
74
+ return fromCharCode(...bytes);
75
+ }
76
+ /** 解码 Ascii */
77
+ function shortStringInJS(buf: Uint8Array, begin: number, length: number): string | undefined {
78
+ if (length < 4) {
79
+ if (length < 2) {
80
+ if (length === 0) return '';
81
+ else {
82
+ const a = buf[begin++];
83
+ if ((a & 0x80) > 1) {
84
+ return;
85
+ }
86
+ return fromCharCode(a);
87
+ }
88
+ } else {
89
+ const a = buf[begin++];
90
+ const b = buf[begin++];
91
+ if ((a & 0x80) > 0 || (b & 0x80) > 0) {
92
+ return;
93
+ }
94
+ if (length < 3) return fromCharCode(a, b);
95
+ const c = buf[begin++];
96
+ if ((c & 0x80) > 0) {
97
+ return;
98
+ }
99
+ return fromCharCode(a, b, c);
100
+ }
101
+ } else {
102
+ const a = buf[begin++];
103
+ const b = buf[begin++];
104
+ const c = buf[begin++];
105
+ const d = buf[begin++];
106
+ if ((a & 0x80) > 0 || (b & 0x80) > 0 || (c & 0x80) > 0 || (d & 0x80) > 0) {
107
+ return;
108
+ }
109
+ if (length < 6) {
110
+ if (length === 4) return fromCharCode(a, b, c, d);
111
+ else {
112
+ const e = buf[begin++];
113
+ if ((e & 0x80) > 0) {
114
+ return;
115
+ }
116
+ return fromCharCode(a, b, c, d, e);
117
+ }
118
+ } else if (length < 8) {
119
+ const e = buf[begin++];
120
+ const f = buf[begin++];
121
+ if ((e & 0x80) > 0 || (f & 0x80) > 0) {
122
+ return;
123
+ }
124
+ if (length < 7) return fromCharCode(a, b, c, d, e, f);
125
+ const g = buf[begin++];
126
+ if ((g & 0x80) > 0) {
127
+ return;
128
+ }
129
+ return fromCharCode(a, b, c, d, e, f, g);
130
+ } else {
131
+ const e = buf[begin++];
132
+ const f = buf[begin++];
133
+ const g = buf[begin++];
134
+ const h = buf[begin++];
135
+ if ((e & 0x80) > 0 || (f & 0x80) > 0 || (g & 0x80) > 0 || (h & 0x80) > 0) {
136
+ return;
137
+ }
138
+ if (length < 10) {
139
+ if (length === 8) return fromCharCode(a, b, c, d, e, f, g, h);
140
+ else {
141
+ const i = buf[begin++];
142
+ if ((i & 0x80) > 0) {
143
+ return;
144
+ }
145
+ return fromCharCode(a, b, c, d, e, f, g, h, i);
146
+ }
147
+ } else if (length < 12) {
148
+ const i = buf[begin++];
149
+ const j = buf[begin++];
150
+ if ((i & 0x80) > 0 || (j & 0x80) > 0) {
151
+ return;
152
+ }
153
+ if (length < 11) return fromCharCode(a, b, c, d, e, f, g, h, i, j);
154
+ const k = buf[begin++];
155
+ if ((k & 0x80) > 0) {
156
+ return;
157
+ }
158
+ return fromCharCode(a, b, c, d, e, f, g, h, i, j, k);
159
+ } else {
160
+ const i = buf[begin++];
161
+ const j = buf[begin++];
162
+ const k = buf[begin++];
163
+ const l = buf[begin++];
164
+ if ((i & 0x80) > 0 || (j & 0x80) > 0 || (k & 0x80) > 0 || (l & 0x80) > 0) {
165
+ return;
166
+ }
167
+ if (length < 14) {
168
+ if (length === 12) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l);
169
+ else {
170
+ const m = buf[begin++];
171
+ if ((m & 0x80) > 0) {
172
+ begin -= 13;
173
+ return;
174
+ }
175
+ return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m);
176
+ }
177
+ } else {
178
+ const m = buf[begin++];
179
+ const n = buf[begin++];
180
+ if ((m & 0x80) > 0 || (n & 0x80) > 0) {
181
+ return;
182
+ }
183
+ if (length < 15) return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n);
184
+ const o = buf[begin++];
185
+ if ((o & 0x80) > 0) {
186
+ return;
187
+ }
188
+ return fromCharCode(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o);
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ /** 字符串解码,无缓存 */
61
196
  export function decode(data: Uint8Array, begin: number, end: number): string {
62
- if (
63
- end - begin <
64
- TEXT_ENCODER_THRESHOLD // 只有小字符串有优化价值,见 benchmark-string.js
65
- ) {
197
+ if (end > data.length) throw new UnexpectedEof();
198
+ const length = end - begin;
199
+ if (length < 16) {
200
+ const result = shortStringInJS(data, begin, length);
201
+ if (result != null) return result;
202
+ }
203
+ // 只有小字符串有优化价值,见 benchmark-string.js
204
+ if (length < TEXT_ENCODER_THRESHOLD) {
66
205
  // 为小字符串优化
67
206
  return decodeJs(data, begin, end);
68
207
  }
69
208
  // 使用系统解码
70
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
71
209
  return textDecoder!.decode(data.subarray(begin, end));
72
210
  }
73
211
 
74
- /** 特别优化字符串解码速度 */
75
- export class StringDecoder {
76
- /** 小字符串缓存 */
77
- private readonly cache = [
78
- undefined, // 0 字节字符串,直接返回
79
- undefined, // 1 字节字符串,使用 String.fromCharCode,兼容 UTF8
80
- new Map<number, string>(), // 2 字节
81
- new Map<number, string>(), // 3 字节
82
- new Map<number, string>(), // 4 字节
83
- new Map<number, string>(), // 5 字节
84
- new Map<number, string>(), // 6 字节
85
- ] as const;
212
+ const keyCache = Array.from<{ value: string; buffer: Uint8Array } | undefined>({ length: 4096 });
86
213
 
87
- /** 字符串解码 */
88
- decode(data: Uint8Array, begin: number, end: number): string {
89
- const length = (end - begin) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
90
- // 这里,length 类型为 Uint32
91
- if (length > 6) return decode(data, begin, end);
214
+ /** 字符串解码,使用缓存 */
215
+ export function decodeKey(data: Uint8Array, begin: number, end: number): string {
216
+ const length = end - begin;
217
+ const cacheKey =
218
+ ((length << 5) ^ (length > 1 ? data[begin] & (data[begin + 1] << 8) : length > 0 ? data[begin] : 0)) & 0xfff;
219
+ let entry = keyCache[cacheKey];
220
+ if (entry != null && entry.buffer.byteLength === length) {
221
+ let i = 0;
222
+ for (; i < length; i++) {
223
+ if (entry.buffer[i] !== data[begin + i]) break;
224
+ }
225
+ if (i === length) return entry.value;
226
+ }
92
227
 
93
- // 这里,length 类型为 0 | 1 | 2 | 3 | 4 | 5 | 6
94
- // 为小字符串优化
95
- if (length === 0) return '';
96
- if (length === 1) return String.fromCharCode(data[begin]);
97
- // number 最多存储 6 字节数据
98
- const cache = this.cache[length];
99
- // 计算缓存 key
100
- let key = 0;
101
- for (let i = begin; i < end; i++) {
102
- // 不要用位运算,JS 位运算只支持 32 位
103
- key = key * 0xff + data[i];
228
+ if (end > data.length) throw new UnexpectedEof();
229
+ let str = length < 16 ? shortStringInJS(data, begin, length) : longStringInJS(data, begin, length);
230
+ if (str == null) {
231
+ // 只有小字符串有优化价值,见 benchmark-string.js
232
+ if (length < TEXT_ENCODER_THRESHOLD) {
233
+ // 为小字符串优化
234
+ str = decodeJs(data, begin, end);
235
+ } else {
236
+ // 使用系统解码
237
+ str = textDecoder!.decode(data.subarray(begin, end));
104
238
  }
105
- const match = cache.get(key);
106
- if (match != null) return match;
107
- const string = decodeJs(data, begin, end);
108
- cache.set(key, string);
109
- return string;
110
239
  }
240
+ entry = { value: str, buffer: data.slice(begin, end) };
241
+ keyCache[cacheKey] = entry;
242
+ return str;
111
243
  }
@@ -53,45 +53,10 @@ function getCharCodeByteLength(string: string): number {
53
53
  return byteLength;
54
54
  }
55
55
 
56
- /** 字符串编码器 */
57
- export class StringEncoder extends TextEncoder {
58
- private readonly cache = new Map<string, Uint8Array>();
56
+ export const getStringByteLength = (): ((v: string) => number) =>
57
+ typeof Buffer?.byteLength == 'function' ? (v) => Buffer.byteLength(v, 'utf8') : getCharCodeByteLength;
59
58
 
60
- /** 编码字符串 */
61
- override encode(value: string): Uint8Array {
62
- if (value.length > 16) {
63
- return super.encode(value);
64
- }
65
- // 优化小字符串
66
- let buf = this.cache.get(value);
67
- if (buf == null) {
68
- let isAscii = true;
69
- for (let index = 0; index < value.length; index++) {
70
- const ch = value.charCodeAt(index);
71
- if (ch >= 128) {
72
- isAscii = false;
73
- break;
74
- }
75
- }
76
- if (isAscii) {
77
- buf = new Uint8Array(value.length);
78
- for (let index = 0; index < value.length; index++) {
79
- const ch = value.charCodeAt(index);
80
- buf[index] = ch;
81
- }
82
- } else {
83
- buf = super.encode(value);
84
- }
85
- this.cache.set(value, buf);
86
- }
87
- return buf;
88
- }
89
-
90
- /** 计算使用的空间 */
91
- byteLength(value: string): number {
92
- if (typeof Buffer == 'function' && typeof Buffer.byteLength == 'function') {
93
- return Buffer.byteLength(value, 'utf8');
94
- }
95
- return getCharCodeByteLength(value);
96
- }
97
- }
59
+ /* c8 ignore next 1 */
60
+ const encoder = typeof TextEncoder == 'function' ? new TextEncoder() : undefined;
61
+ export const getEncodeInto = (): ((v: string, buf: Uint8Array, offset: number) => number) | undefined =>
62
+ encoder?.encodeInto ? (v, buf, offset) => encoder.encodeInto(v, buf.subarray(offset)).written : undefined;
package/src/encoder.ts CHANGED
@@ -34,22 +34,8 @@ export class Encoder extends EncoderBase {
34
34
 
35
35
  /** 获取写入结果 */
36
36
  encode(): Uint8Array {
37
- if (ArrayBuffer.isView(this.value)) {
38
- // 为根 typed array 优化,减少不必要的分配
39
- if (this.value.byteLength < 128) this.ensureCapacity(6 + this.value.byteLength);
40
- else if (this.value.byteLength < 32768) this.ensureCapacity(7 + this.value.byteLength);
41
- else if (this.value.byteLength < 2_147_483_648) this.ensureCapacity(9 + this.value.byteLength);
42
- else this.ensureCapacity(13 + this.value.byteLength);
43
- this.writeTypedArray(this.value);
44
- // 取消注释后运行测试
45
- // if (this.buffer.byteLength !== this.length) {
46
- // // 这里永远无法到达
47
- // throw new Error('Bad buffer size');
48
- // }
49
- } else {
50
- this.ensureCapacity(BLOCK_SIZE);
51
- this.write(this.value);
52
- }
37
+ this.ensureCapacity(BLOCK_SIZE);
38
+ this.write(this.value);
53
39
  if (this.flushedBuffers.length === 0) {
54
40
  if (this.buffer.byteLength === this.length) {
55
41
  // 无需再拷贝一次
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Encoder } from './encoder.js';
2
2
  import { Decoder } from './decoder.js';
3
- import { toUint8Array } from './utils.js';
3
+ export { UnexpectedEof } from './utils.js';
4
4
 
5
5
  /** 编码为 UBJSON */
6
6
  export function encode(value: unknown): Uint8Array {
@@ -10,9 +10,6 @@ export function encode(value: unknown): Uint8Array {
10
10
 
11
11
  /** 解码 UBJSON */
12
12
  export function decode(value: BinaryData): unknown {
13
- const decoder = new Decoder(toUint8Array(value));
13
+ const decoder = new Decoder(value);
14
14
  return decoder.decode();
15
15
  }
16
-
17
- export { StringEncoder } from './common/string-encoder.js';
18
- export { StringDecoder } from './common/string-decoder.js';
@@ -15,7 +15,7 @@ export function decode(): OperatorFunction<BinaryData, unknown> {
15
15
  let end = 0;
16
16
  return observable.subscribe({
17
17
  next(value) {
18
- const data = toUint8Array(value);
18
+ const data = toUint8Array(value, false);
19
19
  if (buffer.length >= end + data.length) {
20
20
  buffer.set(data, end);
21
21
  end += data.length;
@@ -30,7 +30,7 @@ export function decode(): OperatorFunction<BinaryData, unknown> {
30
30
  }
31
31
  while (end - begin > 0) {
32
32
  try {
33
- const helper = new StreamDecoderHelper(buffer.subarray(begin, end));
33
+ const helper = new StreamDecoderHelper(buffer.slice(begin, end));
34
34
  const result = helper.decode();
35
35
  if (result !== undefined) {
36
36
  subscriber.next(result);
package/src/rxjs/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { encode } from './encoder.js';
2
2
  export { decode } from './decoder.js';
3
+ export { UnexpectedEof } from '../utils.js';
@@ -2,6 +2,8 @@ import { Readable, Transform } from 'node:stream';
2
2
  import { StreamEncoder } from './encoder.js';
3
3
  import { StreamDecoder } from './decoder.js';
4
4
 
5
+ export { UnexpectedEof } from '../utils.js';
6
+
5
7
  /** 编码为 UBJSON */
6
8
  export function encode(value: unknown): Readable {
7
9
  if (value == null) {
@@ -12,4 +12,4 @@ export class StreamDecoderHelper extends Decoder {
12
12
  }
13
13
  }
14
14
 
15
- export { UnexpectedEof } from '../common/decoder.js';
15
+ export { UnexpectedEof } from '../utils.js';
package/src/utils.ts CHANGED
@@ -1,10 +1,20 @@
1
1
  /** 支持的数据转为 Uint8Array */
2
- export function toUint8Array(data: BinaryData): Uint8Array {
2
+ export function toUint8Array(data: BinaryData, exact: boolean): Uint8Array {
3
3
  if (data == null || typeof data != 'object' || typeof data.byteLength != 'number') {
4
4
  throw new TypeError('Invalid data');
5
5
  }
6
- if (ArrayBuffer.isView(data)) {
7
- return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
6
+ if (!ArrayBuffer.isView(data)) {
7
+ return new Uint8Array(data);
8
+ }
9
+ if (exact && (data.byteOffset !== 0 || data.buffer.byteLength !== data.byteLength)) {
10
+ return new Uint8Array(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength));
11
+ }
12
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
13
+ }
14
+
15
+ /** 未结束的流 */
16
+ export class UnexpectedEof extends Error {
17
+ constructor() {
18
+ super('Unexpected EOF');
8
19
  }
9
- return new Uint8Array(data, 0, data.byteLength);
10
20
  }
package/tests/decode.js CHANGED
@@ -2,7 +2,7 @@
2
2
  * Tests from https://bitbucket.org/shelacek/ubjson
3
3
  */
4
4
 
5
- import { decode } from '../dist/index.js';
5
+ import { decode, UnexpectedEof } from '../dist/index.js';
6
6
  import { toBuffer } from './.utils.js';
7
7
 
8
8
  test('decode unsupported type', () => {
@@ -66,14 +66,50 @@ test('decode float64', () => {
66
66
  expect(decode(toBuffer('D', 0x40, 0xf8, 0x6a, 0x00, 0x10, 0x00, 0x00, 0x00))).toBe(100_000.003_906_25);
67
67
  });
68
68
 
69
+ test('decode int8 [unexpected eof]', () => {
70
+ expect(() => decode(toBuffer('i'))).toThrowError(UnexpectedEof);
71
+ });
72
+
73
+ test('decode uint8 [unexpected eof]', () => {
74
+ expect(() => decode(toBuffer('U'))).toThrowError(UnexpectedEof);
75
+ });
76
+
77
+ test('decode int16 [unexpected eof]', () => {
78
+ expect(() => decode(toBuffer('I', 0x12))).toThrowError(UnexpectedEof);
79
+ });
80
+
81
+ test('decode int32 [unexpected eof]', () => {
82
+ expect(() => decode(toBuffer('l', 0x12, 0x34, 0x56))).toThrowError(UnexpectedEof);
83
+ });
84
+
85
+ test('decode int64 [unexpected eof]', () => {
86
+ expect(() => decode(toBuffer('L', 0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde))).toThrowError(UnexpectedEof);
87
+ });
88
+
89
+ test('decode float32 [unexpected eof]', () => {
90
+ expect(() => decode(toBuffer('d', 0x3f, 0x80, 0x80))).toThrowError(UnexpectedEof);
91
+ });
92
+
93
+ test('decode float64 [unexpected eof]', () => {
94
+ expect(() => decode(toBuffer('D', 0x40, 0xf8, 0x6a, 0x00, 0x10, 0x00, 0x00))).toThrowError(UnexpectedEof);
95
+ });
96
+
69
97
  test('decode high-precision number [error]', () => {
70
98
  expect(() => decode(toBuffer('H', 'i', 3, '1', '.', '1'))).toThrow();
71
99
  });
72
100
 
101
+ test('decode high-precision number [unexpected eof]', () => {
102
+ expect(() => decode(toBuffer('H', 'i', 3, '1', '.'))).toThrowError(UnexpectedEof);
103
+ });
104
+
73
105
  test('decode char', () => {
74
106
  expect(decode(toBuffer('C', 'a'))).toBe('a');
75
107
  });
76
108
 
109
+ test('decode char [unexpected eof]', () => {
110
+ expect(() => decode(toBuffer('C'))).toThrowError(UnexpectedEof);
111
+ });
112
+
77
113
  test('decode string', () => {
78
114
  expect(decode(toBuffer('S', 'i', 6, 'u', 'b', 'j', 's', 'o', 'n'))).toBe('ubjson');
79
115
  });
@@ -90,8 +126,16 @@ test('decode string (bad size) [error]', () => {
90
126
  expect(() => decode(toBuffer('S', 'i', 0xff, 'x'))).toThrow(/Invalid length/);
91
127
  });
92
128
 
93
- test('decode string (unexpected eof) [error]', () => {
94
- expect(() => decode(toBuffer('S', 'i', 2, 'x'))).toThrow(/Unexpected EOF/);
129
+ test('decode short string (unexpected eof) [error]', () => {
130
+ expect(() => decode(toBuffer('S', 'i', 2, 'x'))).toThrow(UnexpectedEof);
131
+ });
132
+
133
+ test('decode long string (unexpected eof) [error]', () => {
134
+ expect(() => decode(toBuffer('S', 'i', 20, 'x'))).toThrow(UnexpectedEof);
135
+ });
136
+
137
+ test('decode huge string (unexpected eof) [error]', () => {
138
+ expect(() => decode(toBuffer('S', 'U', 200, 'x'))).toThrow(UnexpectedEof);
95
139
  });
96
140
 
97
141
  test('decode ascii string', () => {
@@ -276,8 +320,24 @@ test('decode array of objects of arrays (optimized)', () => {
276
320
  ]);
277
321
  });
278
322
 
279
- test('decode array (strongly typed, unexpected eof, optimized)', () => {
280
- expect(() => decode(toBuffer('[', '$', 'i', '#', 'i', 3, 1, 2))).toThrow();
323
+ test('decode array (strongly typed i8, unexpected eof, optimized)', () => {
324
+ expect(() => decode(toBuffer('[', '$', 'i', '#', 'i', 3, 1, 2))).toThrow(UnexpectedEof);
325
+ });
326
+
327
+ test('decode array (strongly typed u8, unexpected eof, optimized)', () => {
328
+ expect(() => decode(toBuffer('[', '$', 'U', '#', 'i', 3, 1, 2))).toThrow(UnexpectedEof);
329
+ });
330
+
331
+ test('decode array (strongly typed i16, unexpected eof, optimized)', () => {
332
+ expect(() => decode(toBuffer('[', '$', 'I', '#', 'i', 3, 1, 2))).toThrow(UnexpectedEof);
333
+ });
334
+
335
+ test('decode array (strongly typed i32, unexpected eof, optimized)', () => {
336
+ expect(() => decode(toBuffer('[', '$', 'l', '#', 'i', 3, 1, 2))).toThrow(UnexpectedEof);
337
+ });
338
+
339
+ test('decode array (strongly typed i64, unexpected eof, optimized)', () => {
340
+ expect(() => decode(toBuffer('[', '$', 'L', '#', 'i', 3, 1, 2))).toThrow(UnexpectedEof);
281
341
  });
282
342
 
283
343
  test('decode array (strongly typed, invalid length value, optimized)', () => {
@@ -334,6 +394,10 @@ test('decode array (int64, strongly typed, optimized) [use typed array]', () =>
334
394
  ).toThrow();
335
395
  });
336
396
 
397
+ test('decode array (int64, strongly typed, optimized, empty) [use typed array]', () => {
398
+ expect(() => decode(toBuffer('[', '$', 'L', '#', 'i', 0))).toThrow();
399
+ });
400
+
337
401
  test('decode array (float32, strongly typed, optimized) [use typed array]', () => {
338
402
  const actual = decode(toBuffer('[', '$', 'd', '#', 'i', 2, 0x3e, 0x80, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00));
339
403
  expect(actual).toBeInstanceOf(Float32Array);
package/tests/e2e.js CHANGED
@@ -299,7 +299,6 @@ test('encode/decode model', () => {
299
299
  test('encode/decode complex object (no encodeInto)', () => {
300
300
  // eslint-disable-next-line @typescript-eslint/unbound-method
301
301
  const encodeInto = TextEncoder.prototype.encodeInto;
302
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
303
302
  // @ts-expect-error 移除 encodeInto 以测试兼容性
304
303
  TextEncoder.prototype.encodeInto = undefined;
305
304
  const expected = {
@@ -399,3 +398,15 @@ test('decode Int8Array', () => {
399
398
  const obj = { a: 1, b: undefined, c: { d: 2, e: undefined, f: null } };
400
399
  expect(decode(new Int8Array(encode(obj).buffer))).toEqual(obj);
401
400
  });
401
+
402
+ test('decode invalid __proto__', () => {
403
+ const obj = decode(encode(JSON.parse('{"__proto__":"xxx"}')));
404
+ expect(obj).toEqual(JSON.parse('{"__proto__":"xxx"}'));
405
+ expect(Object.getPrototypeOf(obj)).toBe(Object.prototype);
406
+ });
407
+
408
+ test('decode null __proto__', () => {
409
+ const obj = decode(encode(JSON.parse('{"__proto__":null}')));
410
+ expect(obj).toEqual(JSON.parse('{"__proto__":null}'));
411
+ expect(Object.getPrototypeOf(obj)).toBe(Object.prototype);
412
+ });
package/tests/encode.js CHANGED
@@ -69,8 +69,8 @@ test('encode char', () => {
69
69
  expect(toArray(encode('a'))).toEqual(toArray('C', 'a'));
70
70
  });
71
71
 
72
- test('encode char 128', () => {
73
- expect(toArray(encode('\u00CC'))).toEqual(toArray('C', '\u00CC'));
72
+ test('encode char 127', () => {
73
+ expect(toArray(encode('\u007F'))).toEqual(toArray('C', '\u007F'));
74
74
  });
75
75
 
76
76
  test('encode char 257', () => {
@@ -85,15 +85,27 @@ test('encode string', () => {
85
85
  expect(toArray(encode('ubjson'))).toEqual(toArray('S', 'i', 6, ...'ubjson'));
86
86
  });
87
87
 
88
- test('encode string (u8 len)', () => {
88
+ test('encode string (len = 60)', () => {
89
89
  // 不生成 u8
90
- expect(toArray(encode('ubjson'.repeat(10)))).toEqual(toArray('S', 'I', 0, 6 * 10, ...'ubjson'.repeat(10)));
90
+ const str = 'ubjson'.repeat(10);
91
+ expect(toArray(encode(str))).toEqual(toArray('S', 'i', str.length, ...str));
92
+ });
93
+
94
+ test('encode string (len = 120)', () => {
95
+ // 不生成 u8
96
+ const str = 'ubjson'.repeat(20);
97
+ expect(toArray(encode(str))).toEqual(toArray('S', 'i', str.length, ...str));
98
+ });
99
+
100
+ test('encode string (len = 180)', () => {
101
+ // 不生成 u8
102
+ const str = 'ubjson'.repeat(30);
103
+ expect(toArray(encode(str))).toEqual(toArray('S', 'I', 0, str.length, ...str));
91
104
  });
92
105
 
93
106
  test('encode string (no encodeInto)', () => {
94
107
  // eslint-disable-next-line @typescript-eslint/unbound-method
95
108
  const encodeInto = TextEncoder.prototype.encodeInto;
96
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
97
109
  // @ts-expect-error 移除 encodeInto 以测试兼容性
98
110
  TextEncoder.prototype.encodeInto = undefined;
99
111
  expect(toArray(encode('ubjson'))).toEqual(toArray('S', 'i', 6, ...'ubjson'));
@@ -357,31 +369,36 @@ test('encode huge data (~128M)', () => {
357
369
  );
358
370
  });
359
371
 
360
- test('encode huge data (128M)', () => {
361
- const obj = [new Uint8Array(128 * 1024 * 1024)];
372
+ test('encode huge data (128M - 9)', () => {
373
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 9)];
362
374
  obj[0][0] = 0x12;
363
375
  obj[0][1] = 0x34;
364
376
  const encoded = encode(obj);
365
- expect(encoded.length).toBe(128 * 1024 * 1024 + 9 + 2);
366
- expect(toArray(encoded.slice(0, 12))).toEqual(toArray('[', '[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x12, 0x34));
377
+ expect(encoded.length).toBe(128 * 1024 * 1024 + 2);
378
+ // (128 * 1024 * 1024 - 9) === 0x7fffff7
379
+ expect(toArray(encoded.slice(0, 12))).toEqual(
380
+ toArray('[', '[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x12, 0x34),
381
+ );
367
382
  });
368
383
 
369
- test('encode huge data (128M + 1) [error]', () => {
370
- const obj = [new Uint8Array(128 * 1024 * 1024 + 1)];
384
+ test('encode huge data (128M - 8) [error]', () => {
385
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 8)];
371
386
  expect(() => encode(obj)).toThrow(/Buffer has exceed max size/);
372
387
  });
373
388
 
374
389
  test('encode huge data (256M)', () => {
375
- const obj = [new Uint8Array(128 * 1024 * 1024), new Uint8Array(128 * 1024 * 1024)];
390
+ const obj = [new Uint8Array(128 * 1024 * 1024 - 9), new Uint8Array(128 * 1024 * 1024 - 9)];
376
391
  obj[0][0] = 0x12;
377
392
  obj[0][1] = 0x34;
378
393
  obj[1][0] = 0x56;
379
394
  obj[1][1] = 0x78;
380
395
  const encoded = encode(obj);
381
- expect(encoded.length).toBe(128 * 1024 * 1024 * 2 + 9 * 2 + 2);
382
- expect(toArray(encoded.slice(0, 12))).toEqual(toArray('[', '[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x12, 0x34));
383
- expect(toArray(encoded.slice(128 * 1024 * 1024 + 10, 128 * 1024 * 1024 + 10 + 11))).toEqual(
384
- toArray('[', '$', 'U', '#', 'l', 0x8, 0, 0, 0, 0x56, 0x78),
396
+ expect(encoded.length).toBe(128 * 1024 * 1024 * 2 + 2);
397
+ expect(toArray(encoded.slice(0, 12))).toEqual(
398
+ toArray('[', '[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x12, 0x34),
399
+ );
400
+ expect(toArray(encoded.slice(128 * 1024 * 1024 + 1, 128 * 1024 * 1024 + 1 + 11))).toEqual(
401
+ toArray('[', '$', 'U', '#', 'l', 0x7, 0xff, 0xff, 0xf7, 0x56, 0x78),
385
402
  );
386
403
  expect(toArray(encoded.slice(-1))).toEqual(toArray(']'));
387
404
  });