msgpackr 1.7.0-alpha1 → 1.7.0-alpha4

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/struct.js CHANGED
@@ -1,178 +1,481 @@
1
- // first four bits
2
- // 0000 - unsigned int
3
- // 0010 - float32
4
- // 0011 - float32
5
- // 0100 - float32
6
- // 0101 - float32
7
- // 0110 - latin string reference
8
- // 0111 - plain reference
9
- // 1000 - structure reference
10
- // 1001 - random access structure reference
11
- // 1010 - float32
12
- // 1011 - float32
13
- // 1100 - float32
14
- // 1101 - float32
15
- // 1110 - constants and 3-byte strings
16
- // 1111 - negative int
17
1
 
18
- // first three bits
19
- // 000 - unsigned int
20
- // 001 - float32
21
- // 010 - float32
22
- // 011 - latin string reference
23
- // 100 - reference
24
- // 101 - float32
25
- // 110 - float32
26
- // 111 - constants and 3-byte strings
2
+ /*
27
3
 
28
- import { setWriteStructSlots, RECORD_SYMBOL } from './pack.js'
29
- import { setReadStruct, unpack, mult10 } from './unpack.js';
30
- const hasNonLatin = /[\u0080-\uFFFF]/;
31
- const float32Headers = [false, true, true, false, false, true, true, false]
32
- setWriteStructSlots(writeStruct);
33
- function writeStruct(object, target, position, structures, makeRoom, pack) {
34
- let transition = structures.transitions || false
35
- let newTransitions = 0
36
- let keyCount = 0;
37
- let start = position;
38
- position += 4;
39
- let queuedReferences = [];
40
- let uint32 = target.uint32 || (target.uint32 = new Uint32Array(target.buffer));
4
+ For "any-data":
5
+ 32-55 - record with record ids (-32)
6
+ 56 - 8-bit record ids
7
+ 57 - 16-bit record ids
8
+ 58 - 24-bit record ids
9
+ 59 - 32-bit record ids
10
+ 250-255 - followed by typed fixed width values
11
+ 64-250 msgpackr/cbor/paired data
12
+ arrays and strings within arrays are handled by paired encoding
13
+
14
+ Structure encoding:
15
+ (type - string (using paired encoding))+
16
+
17
+ Type encoding
18
+ encoding byte - fixed width byte - next reference+
19
+
20
+ Encoding byte:
21
+ first bit:
22
+ 0 - inline
23
+ 1 - reference
24
+ second bit:
25
+ 0 - data or number
26
+ 1 - string
27
+
28
+ remaining bits:
29
+ character encoding - ISO-8859-x
30
+
31
+
32
+ null (0xff)+ 0xf6
33
+ null (0xff)+ 0xf7
34
+
35
+ */
36
+
37
+
38
+ import {setWriteStructSlots, RECORD_SYMBOL, addExtension} from './pack.js'
39
+ import {setReadStruct, mult10, readString} from './unpack.js';
40
+ const ASCII = 3; // the MIBenum from https://www.iana.org/assignments/character-sets/character-sets.xhtml (and other character encodings could be referenced by MIBenum)
41
+ const NUMBER = 0;
42
+ const UTF8 = 2;
43
+ const OBJECT_DATA = 1;
44
+ const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
45
+ const float32Headers = [false, true, true, false, false, true, true, false];
46
+ let updatedPosition;
47
+ const hasNodeBuffer = typeof Buffer !== 'undefined'
48
+ let textEncoder
49
+ try {
50
+ textEncoder = new TextEncoder()
51
+ } catch (error) {}
52
+ const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
53
+ return target.utf8Write(string, position, 0xffffffff)
54
+ } : (textEncoder && textEncoder.encodeInto) ?
55
+ function(target, string, position) {
56
+ return textEncoder.encodeInto(string, target.subarray(position)).written
57
+ } : false
58
+
59
+ const TYPE = Symbol('type');
60
+ const PARENT = Symbol('parent');
61
+ setWriteStructSlots(writeStruct, prepareStructures);
62
+ function writeStruct(object, target, position, structures, makeRoom, pack, packr) {
63
+ let typedStructs = packr.typedStructs || (packr.typedStructs = []);
64
+ // note that we rely on pack.js to load stored structures before we get to this point
41
65
  let targetView = target.dataView;
42
- let encoded;
43
- let stringData = '';
66
+ let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
44
67
  let safeEnd = target.length - 10;
68
+ let start = position;
69
+ if (position > safeEnd) {
70
+ let lastStart = start;
71
+ target = makeRoom(position);
72
+ targetView = target.dataView;
73
+ position -= lastStart;
74
+ refsStartPosition -= lastStart;
75
+ start = 0;
76
+ safeEnd = target.length - 10
77
+ }
78
+
79
+ let refOffset, refPosition = refsStartPosition;
80
+
81
+ let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
82
+ let nextId = typedStructs.nextId || typedStructs.length;
83
+ let headerSize =
84
+ nextId < 0xf ? 1 :
85
+ nextId < 0xf0 ? 2 :
86
+ nextId < 0xf000 ? 3 :
87
+ nextId < 0xf00000 ? 4 : 0;
88
+ if (headerSize === 0)
89
+ return 0;
90
+ position += headerSize;
91
+ let queuedReferences = [];
92
+ let usedLatin0;
93
+ let keyIndex = 0;
45
94
  for (let key in object) {
46
- let nextTransition = transition[key]
95
+ let value = object[key];
96
+ let nextTransition = transition[key];
47
97
  if (!nextTransition) {
48
- return 0; // bail
49
- //nextTransition = transition[key] = Object.create(null)
50
- //newTransitions++
98
+ transition[key] = nextTransition = {
99
+ key,
100
+ parent: transition,
101
+ enumerationOffset: 0,
102
+ ascii0: null,
103
+ ascii8: null,
104
+ num8: null,
105
+ string16: null,
106
+ object16: null,
107
+ num32: null,
108
+ float64: null
109
+ };
51
110
  }
52
111
  if (position > safeEnd) {
53
- let newPosition = position - start;
54
- target = makeRoom(position)
55
- position = newPosition;
56
- start = 0
112
+ let lastStart = start;
113
+ target = makeRoom(position);
114
+ targetView = target.dataView;
115
+ position -= lastStart;
116
+ refsStartPosition -= lastStart;
117
+ refPosition -= lastStart;
118
+ start = 0;
57
119
  safeEnd = target.length - 10
58
120
  }
59
- transition = nextTransition
60
- let value = object[key];
61
121
  switch (typeof value) {
62
122
  case 'number':
63
- if (value >>> 0 === value && value < 0x20000000) {
64
- encoded = value;
123
+ let number = value;
124
+ if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
125
+ if (number < 0xf6 && number >= 0 && (nextTransition.num8 || number < 0x20 && !nextTransition.num32)) {
126
+ transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1);
127
+ target[position++] = number;
128
+ } else {
129
+ transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
130
+ targetView.setUint32(position, number, true);
131
+ position += 4;
132
+ }
65
133
  break;
66
- } else if (value < 0x100000000 && value >= -0x80000000) {
67
- targetView.setFloat32(position, value, true)
134
+ } else if (number < 0x100000000 && number >= -0x80000000) {
135
+ targetView.setFloat32(position, number, true);
68
136
  if (float32Headers[target[position + 3] >>> 5]) {
69
137
  let xShifted
70
138
  // this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
71
- if (((xShifted = value * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
139
+ if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
140
+ transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
72
141
  position += 4;
73
- continue;
142
+ break;
74
143
  }
75
144
  }
76
145
  }
77
- // fall back to msgpack encoding
78
- queuedReferences.push(value, position - start);
79
- position += 4;
80
- continue;
146
+ transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8);
147
+ targetView.setFloat64(position, number, true);
148
+ position += 8;
149
+ break;
81
150
  case 'string':
82
- if (hasNonLatin.test(value)) {
83
- queuedReferences.push(value, position - start);
84
- position += 4;
85
- continue;
151
+ let strLength = value.length;
152
+ refOffset = refPosition - refsStartPosition;
153
+ if ((strLength << 2) + position > safeEnd) {
154
+ let lastStart = start;
155
+ target = makeRoom(refPosition);
156
+ targetView = target.dataView;
157
+ position -= lastStart;
158
+ refsStartPosition -= lastStart;
159
+ refPosition -= lastStart;
160
+ start = 0;
161
+ safeEnd = target.length - 10
162
+ }
163
+ if (strLength > ((0xff00 + refOffset) >> 2)) {
164
+ queuedReferences.push(key, value, position - start);
165
+ break;
86
166
  }
87
- if (value.length < 4) { // we can inline really small strings
88
- encoded = 0xf8000000 + (value.length << 24) + (value.charCodeAt(0) << 16) + (value.charCodeAt(1) << 8) + (value.charCodeAt(2) || 0)
89
- // TODO: determining remaining and make max value be a ratio of that (probably 1/256th)
90
- } else if (value.length < 256 && stringData.length < 61440) {
91
- // bundle these strings
92
- encoded = 0x60000000 | (value.length << 16) | stringData.length;
93
- stringData += value;
94
- } else { // else queue it
95
- queuedReferences.push(value, position - start);
96
- position += 4;
97
- continue;
167
+ let isNotAscii
168
+ let strStart = refPosition;
169
+ if (strLength < 0x40) {
170
+ let i, c1, c2;
171
+ for (i = 0; i < strLength; i++) {
172
+ c1 = value.charCodeAt(i)
173
+ if (c1 < 0x80) {
174
+ target[refPosition++] = c1
175
+ } else if (c1 < 0x800) {
176
+ isNotAscii = true;
177
+ target[refPosition++] = c1 >> 6 | 0xc0
178
+ target[refPosition++] = c1 & 0x3f | 0x80
179
+ } else if (
180
+ (c1 & 0xfc00) === 0xd800 &&
181
+ ((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
182
+ ) {
183
+ isNotAscii = true;
184
+ c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff)
185
+ i++
186
+ target[refPosition++] = c1 >> 18 | 0xf0
187
+ target[refPosition++] = c1 >> 12 & 0x3f | 0x80
188
+ target[refPosition++] = c1 >> 6 & 0x3f | 0x80
189
+ target[refPosition++] = c1 & 0x3f | 0x80
190
+ } else {
191
+ isNotAscii = true;
192
+ target[refPosition++] = c1 >> 12 | 0xe0
193
+ target[refPosition++] = c1 >> 6 & 0x3f | 0x80
194
+ target[refPosition++] = c1 & 0x3f | 0x80
195
+ }
196
+ }
197
+ } else {
198
+ refPosition += encodeUtf8(target, value, refPosition);
199
+ isNotAscii = refPosition - strStart > strLength;
200
+ }
201
+ if (refOffset < 0x100) {
202
+ if (isNotAscii)
203
+ transition = nextTransition.string8 || createTypeTransition(nextTransition, UTF8, 1);
204
+ else
205
+ transition = nextTransition.ascii8 || createTypeTransition(nextTransition, ASCII, 1);
206
+ target[position++] = refOffset;
207
+ } else {
208
+ if (isNotAscii)
209
+ transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2);
210
+ else
211
+ transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
212
+ targetView.setUint16(position, refOffset, true);
213
+ position += 2;
98
214
  }
99
215
  break;
100
216
  case 'object':
101
217
  if (value) {
102
- queuedReferences.push(value, position - start);
103
- position += 4;
104
- continue;
218
+ //transition = nextTransition.object16 || createTypeTransition(nextTransition, OBJECT_DATA, 2);
219
+ queuedReferences.push(key, value, keyIndex);
220
+ break;
105
221
  } else { // null
106
- encoded = 0xe0000000;
222
+ nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
223
+ if (nextTransition) {
224
+ transition = nextTransition;
225
+ position = updatedPosition;
226
+ } else queuedReferences.push(key, value, keyIndex);
107
227
  }
108
228
  break;
109
229
  case 'boolean':
110
- encoded = value ? 0xe3000000 : 0xe2000000;
230
+ transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1);
231
+ target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
111
232
  break;
112
233
  case 'undefined':
113
- encoded = 0xe1000000;
234
+ nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
235
+ if (nextTransition) {
236
+ transition = nextTransition;
237
+ position = updatedPosition;
238
+ } else queuedReferences.push(key, value, keyIndex);
114
239
  break;
115
240
  }
116
- targetView.setUint32(position, encoded, true);
117
- position += 4;
241
+ keyIndex++;
118
242
  }
119
- let recordId = transition[RECORD_SYMBOL]
120
- if (!(recordId < 1024)) {
121
- // for now just punt and go back to writeObject
122
- return 0;
123
- // newRecord(transition, transition.__keys__ || Object.keys(object), newTransitions, true)
124
- }
125
- let stringLength = stringData.length;
126
- if (stringData) {
127
- if (position + stringLength > safeEnd) {
128
- target = makeRoom(position + stringLength);
129
- }
130
- position += target.latin1Write(stringData, position, 0xffffffff);
131
- }
132
- target[start] = recordId >> 8;
133
- target[start + 1] = recordId & 0xff;
134
- target[start + 2] = stringLength >> 8;
135
- target[start + 3] = stringLength & 0xff;
136
- let queued32BitReferences;
243
+
137
244
  for (let i = 0, l = queuedReferences.length; i < l;) {
245
+ let key = queuedReferences[i++];
138
246
  let value = queuedReferences[i++];
139
- let slotOffset = queuedReferences[i++] + start;
140
- let offset = position - slotOffset;
141
- if (offset < 0x1f000000) {
142
- targetView.setUint32(slotOffset, 0x80000000 | (offset), true);
247
+ let propertyIndex = queuedReferences[i++];
248
+ let nextTransition = transition[key];
249
+ if (!nextTransition) {
250
+ transition[key] = nextTransition = {
251
+ key,
252
+ parent: transition,
253
+ enumerationOffset: propertyIndex - keyIndex,
254
+ ascii0: null,
255
+ ascii8: null,
256
+ num8: null,
257
+ string16: null,
258
+ object16: null,
259
+ num32: null,
260
+ float64: null
261
+ };
262
+ }
263
+ let newPosition;
264
+ if (value) {
265
+ /*if (typeof value === 'string') { // TODO: we could re-enable long strings
266
+ if (position + value.length * 3 > safeEnd) {
267
+ target = makeRoom(position + value.length * 3);
268
+ position -= start;
269
+ targetView = target.dataView;
270
+ start = 0;
271
+ }
272
+ newPosition = position + target.utf8Write(value, position, 0xffffffff);
273
+ } else { */
274
+ let size;
275
+ refOffset = refPosition - refsStartPosition;
276
+ if (refOffset < 0xff00) {
277
+ transition = nextTransition.object16;
278
+ if (transition)
279
+ size = 2;
280
+ else if ((transition = nextTransition.object32))
281
+ size = 4;
282
+ else {
283
+ transition = createTypeTransition(nextTransition, OBJECT_DATA, 2);
284
+ size = 2;
285
+ }
286
+ } else {
287
+ transition = nextTransition.object32 || createTypeTransition(nextTransition, OBJECT_DATA, 4);
288
+ size = 4;
289
+ }
290
+ newPosition = pack(value, refPosition);
291
+ //}
292
+ if (typeof newPosition === 'object') {
293
+ // re-allocated
294
+ refPosition = newPosition.position;
295
+ targetView = newPosition.targetView;
296
+ target = newPosition.target;
297
+ refsStartPosition -= start;
298
+ position -= start;
299
+ start = 0;
300
+ } else
301
+ refPosition = newPosition;
302
+ if (size === 2) {
303
+ targetView.setUint16(position, refOffset, true);
304
+ position += 2;
305
+ } else {
306
+ targetView.setUint32(position, refOffset, true);
307
+ position += 4;
308
+ }
143
309
  } else {
144
- if (!queued32BitReferences)
145
- queued32BitReferences = [];
146
- queued32BitReferences.push({slotOffset, offset: position - start});
310
+ transition = nextTransition.object16 || createTypeTransition(nextTransition, OBJECT_DATA, 2);
311
+ targetView.setInt16(position, value === null ? -10 : -9, true);
312
+ position += 2;
147
313
  }
148
- let newPosition = pack(value, position);
149
- if (typeof newPosition === 'object') {
150
- // re-allocated
151
- position = newPosition.position;
152
- targetView = newPosition.targetView;
153
- start = 0;
154
- } else
155
- position = newPosition;
314
+ keyIndex++;
156
315
  }
157
- if (queued32BitReferences) {
158
- // TODO: makeRoom
159
- for (let i = 0, l = queued32BitReferences.length; i < l; i++) {
160
- let ref = queued32BitReferences[i];
161
- targetView.setUint32(ref.slotOffset, 0xa0000000 - ((l - i) << 2), true);
162
- targetView.setUint32(position, ref.offset, true);
163
- position += 4;
316
+
317
+
318
+ let recordId = transition[RECORD_SYMBOL];
319
+ if (recordId == null) {
320
+ recordId = packr.typedStructs.length;
321
+ let structure = [];
322
+ let nextTransition = transition;
323
+ let key, type;
324
+ while ((type = nextTransition.__type) !== undefined) {
325
+ let size = nextTransition.__size;
326
+ nextTransition = nextTransition.__parent;
327
+ key = nextTransition.key;
328
+ let property = [type, size, key];
329
+ if (nextTransition.enumerationOffset)
330
+ property.push(nextTransition.enumerationOffset);
331
+ structure.push(property);
332
+ nextTransition = nextTransition.parent;
164
333
  }
334
+ structure.reverse();
335
+ transition[RECORD_SYMBOL] = recordId;
336
+ packr.typedStructs[recordId] = structure;
337
+ pack(null, 0, true); // special call to notify that structures have been updated
338
+ }
339
+
340
+
341
+ switch (headerSize) {
342
+ case 1:
343
+ if (recordId >= 0x10) return 0;
344
+ target[start] = recordId + 0x20;
345
+ break;
346
+ case 2:
347
+ if (recordId >= 0x100) return 0;
348
+ target[start] = 0x38;
349
+ target[start + 1] = recordId;
350
+ break;
351
+ case 3:
352
+ if (recordId >= 0x10000) return 0;
353
+ target[start] = 0x39;
354
+ target.setUint16(start + 1, recordId, true);
355
+ break;
356
+ case 4:
357
+ if (recordId >= 0x1000000) return 0;
358
+ target.setUint32(start, (recordId << 8) + 0x3a, true);
359
+ break;
165
360
  }
166
361
 
167
- return position;
362
+ if (position < refsStartPosition) {
363
+ if (refsStartPosition === refPosition)
364
+ return position; // no refs
365
+ // adjust positioning
366
+ target.copyWithin(position, refsStartPosition, refPosition);
367
+ refPosition += position - refsStartPosition;
368
+ typedStructs.lastStringStart = position - start;
369
+ } else if (position > refsStartPosition) {
370
+ if (refsStartPosition === refPosition)
371
+ return position; // no refs
372
+ typedStructs.lastStringStart = position - start;
373
+ return writeStruct(object, target, start, structures, makeRoom, pack, packr);
374
+ }
375
+ return refPosition;
376
+ }
377
+ function anyType(transition, position, targetView, value) {
378
+ let nextTransition;
379
+ if ((nextTransition = transition.ascii8 || transition.num8)) {
380
+ targetView.setInt8(position, value, true);
381
+ updatedPosition = position + 1;
382
+ return nextTransition;
383
+ }
384
+ if ((nextTransition = transition.string16 || transition.object16)) {
385
+ targetView.setInt16(position, value, true);
386
+ updatedPosition = position + 2;
387
+ return nextTransition;
388
+ }
389
+ if (nextTransition = transition.num32) {
390
+ targetView.setUint32(position, 0xe0000100 + value, true);
391
+ updatedPosition = position + 4;
392
+ return nextTransition;
393
+ }
394
+ // transition.float64
395
+ if (nextTransition = transition.num64) {
396
+ targetView.setFloat64(position, NaN, true);
397
+ targetView.setInt8(position, value);
398
+ updatedPosition = position + 8;
399
+ return nextTransition;
400
+ }
401
+ updatedPosition = position;
402
+ // TODO: can we do an "any" type where we defer the decision?
403
+ return;
404
+ }
405
+ function createTypeTransition(transition, type, size) {
406
+ let typeName = TYPE_NAMES[type] + (size << 3);
407
+ let newTransition = transition[typeName] || (transition[typeName] = Object.create(null));
408
+ newTransition.__type = type;
409
+ newTransition.__size = size;
410
+ newTransition.__parent = transition;
411
+ return newTransition;
412
+ }
413
+ function onLoadedStructures(sharedData) {
414
+ if (!(sharedData instanceof Map))
415
+ return sharedData;
416
+ let typed = sharedData.get('typed') || [];
417
+ if (Object.isFrozen(typed))
418
+ typed = typed.map(structure => structure.slice(0));
419
+ let named = sharedData.get('named');
420
+ let transitions = Object.create(null);
421
+ for (let i = 0, l = typed.length; i < l; i++) {
422
+ let structure = typed[i];
423
+ let transition = transitions;
424
+ for (let [type, size, key] of structure) {
425
+ let nextTransition = transition[key];
426
+ if (!nextTransition) {
427
+ transition[key] = nextTransition = {
428
+ key,
429
+ parent: transition,
430
+ enumerationOffset: 0,
431
+ ascii0: null,
432
+ ascii8: null,
433
+ num8: null,
434
+ string16: null,
435
+ object16: null,
436
+ num32: null,
437
+ float64: null
438
+ };
439
+ }
440
+ transition = createTypeTransition(nextTransition, type, size);
441
+ }
442
+ transition[RECORD_SYMBOL] = i;
443
+ }
444
+ typed.transitions = transitions;
445
+ this.typedStructs = typed;
446
+ this.lastTypedStructuresLength = typed.length;
447
+ return named;
168
448
  }
169
449
  var sourceSymbol = Symbol('source')
170
- function readStruct(src, position, srcEnd, structure, unpackr) {
171
- var stringLength = (src[position++] << 8) | src[position++];
450
+ function readStruct(src, position, srcEnd, unpackr) {
451
+ // var stringLength = (src[position++] << 8) | src[position++];
452
+ let recordId = src[position++] - 0x20;
453
+ if (recordId >= 24) {
454
+ switch(recordId) {
455
+ case 24: recordId = src[position++]; break;
456
+ // little endian:
457
+ case 25: recordId = src[position++] + (src[position++] << 8); break;
458
+ case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
459
+ case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
460
+ }
461
+ }
462
+ let structure = unpackr.typedStructs?.[recordId];
463
+ if (!structure) {
464
+ // copy src buffer because getStructures will override it
465
+ src = Uint8Array.prototype.slice.call(src, position, srcEnd);
466
+ srcEnd -= position;
467
+ position = 0;
468
+ unpackr._mergeStructures(unpackr.getStructures());
469
+ if (!unpackr.typedStructs)
470
+ throw new Error('Could not find any shared typed structures');
471
+ unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
472
+ structure = unpackr.typedStructs[recordId];
473
+ if (!structure)
474
+ throw new Error('Could not find typed structure ' + recordId);
475
+ }
172
476
  var construct = structure.construct;
173
- var srcString;
174
477
  if (!construct) {
175
- construct = structure.construct = function() {
478
+ construct = structure.construct = function LazyObject() {
176
479
  }
177
480
  var prototype = construct.prototype;
178
481
  Object.defineProperty(prototype, 'toJSON', {
@@ -187,74 +490,227 @@ function readStruct(src, position, srcEnd, structure, unpackr) {
187
490
  },
188
491
  // not enumerable or anything
189
492
  });
493
+ let currentOffset = 0;
494
+ let lastRefProperty;
495
+ let properties = [];
190
496
  for (let i = 0, l = structure.length; i < l; i++) {
191
- let key = structure[i];
192
- Object.defineProperty(prototype, key, {
193
- get() {
194
- let source = this[sourceSymbol];
195
- let src = source.src;
196
- //let uint32 = src.uint32 || (src.uint32 = new Uint32Array(src.buffer, src.byteOffset, src.byteLength));
197
- let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
198
- let position = source.position + (i << 2);
199
- let value = dataView.getUint32(position, true);
200
- let start;
201
- switch (value >>> 29) {
202
- case 0:
203
- return value;
204
- case 3:
205
- if (value & 0x10000000) {
206
- start = (value & 0xffff) + position;
207
- return src.toString('utf8', start, start + ((value >> 16) & 0x7ff));
208
- } else {
209
- if (!srcString) {
210
- start = source.position + (l << 2);
211
- srcString = src.toString('latin1', start, start + stringLength);
212
- }
213
- start = value & 0xffff;
214
- return srcString.slice(start, start + ((value >> 16) & 0x7ff));
215
- }
216
- case 4:
217
- start = (0x1fffffff & value) + position;
218
- let end = srcEnd;
219
- for (let next = i + 1; next < l; next++) {
220
- position = source.position + (next << 2);
221
- let nextValue = dataView.getUint32(position, true);;
222
- if ((nextValue & 0xe0000000) == -0x80000000) {
223
- end = (0x1fffffff & nextValue) + position;
497
+ let definition = structure[i];
498
+ let [ type, size, key, enumerationOffset ] = definition;
499
+ let property = {
500
+ key,
501
+ offset: currentOffset,
502
+ }
503
+ if (enumerationOffset)
504
+ properties.splice(i + enumerationOffset, 0, property);
505
+ else
506
+ properties.push(property);
507
+ let getRef;
508
+ switch(size) { // TODO: Move into a separate function
509
+ case 0: getRef = () => 0; break;
510
+ case 1:
511
+ getRef = (source, position) => {
512
+ let ref = source.src[position + property.offset];
513
+ return ref >= 0xf6 ? toConstant(ref) : ref;
514
+ };
515
+ break;
516
+ case 2:
517
+ getRef = (source, position) => {
518
+ let src = source.src;
519
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
520
+ let ref = dataView.getUint16(position + property.offset, true);
521
+ return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
522
+ };
523
+ break;
524
+ case 4:
525
+ getRef = (source, position) => {
526
+ let src = source.src;
527
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
528
+ let ref = dataView.getUint32(position + property.offset, true);
529
+ return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
530
+ };
531
+ break;
532
+ }
533
+ property.getRef = getRef;
534
+ currentOffset += size;
535
+ let get;
536
+ switch(type) {
537
+ case ASCII:
538
+ if (lastRefProperty && !lastRefProperty.next)
539
+ lastRefProperty.next = property;
540
+ lastRefProperty = property;
541
+ property.multiGetCount = 0;
542
+ get = function() {
543
+ let source = this[sourceSymbol];
544
+ let src = source.src;
545
+ let position = source.position;
546
+ let refStart = currentOffset + position;
547
+ let ref = getRef(source, position);
548
+ if (typeof ref !== 'number') return ref;
549
+
550
+ let end, next = property.next;
551
+ while(next) {
552
+ end = next.getRef(source, position);
553
+ if (typeof end === 'number')
554
+ break;
555
+ else
556
+ end = null;
557
+ next = next.next;
558
+ }
559
+ if (end == null)
560
+ end = source.srcEnd - refStart;
561
+ if (source.srcString) {
562
+ return source.srcString.slice(ref, end);
563
+ }
564
+ /*if (property.multiGetCount > 0) {
565
+ let asciiEnd;
566
+ next = firstRefProperty;
567
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
568
+ do {
569
+ asciiEnd = dataView.getUint16(source.position + next.offset, true);
570
+ if (asciiEnd < 0xff00)
224
571
  break;
572
+ else
573
+ asciiEnd = null;
574
+ } while((next = next.next));
575
+ if (asciiEnd == null)
576
+ asciiEnd = source.srcEnd - refStart
577
+ source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
578
+ return source.srcString.slice(ref, end);
579
+ }
580
+ if (source.prevStringGet) {
581
+ source.prevStringGet.multiGetCount += 2;
582
+ } else {
583
+ source.prevStringGet = property;
584
+ property.multiGetCount--;
585
+ }*/
586
+ return readString(src, ref + refStart, end - ref);
587
+ //return src.toString('latin1', ref + refStart, end + refStart);
588
+ };
589
+ break;
590
+ case UTF8: case OBJECT_DATA:
591
+ if (lastRefProperty && !lastRefProperty.next)
592
+ lastRefProperty.next = property;
593
+ lastRefProperty = property;
594
+ get = function() {
595
+ let source = this[sourceSymbol];
596
+ let position = source.position;
597
+ let refStart = currentOffset + position;
598
+ let ref = getRef(source, position);
599
+ if (typeof ref !== 'number') return ref;
600
+ let src = source.src;
601
+ let end, next = property.next;
602
+ while(next) {
603
+ end = next.getRef(source, position);
604
+ if (typeof end === 'number')
605
+ break;
606
+ else
607
+ end = null;
608
+ next = next.next;
609
+ }
610
+ if (end == null)
611
+ end = source.srcEnd - refStart;
612
+ if (type === UTF8) {
613
+ return src.toString('utf8', ref + refStart, end + refStart);
614
+ } else {
615
+ return unpackr.unpack(src, { start: ref + refStart, end: end + refStart }); // could reuse this object
616
+ }
617
+ };
618
+ break;
619
+ case NUMBER:
620
+ switch(size) {
621
+ case 4:
622
+ get = function () {
623
+ let source = this[sourceSymbol];
624
+ let src = source.src;
625
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
626
+ let position = source.position + property.offset;
627
+ let value = dataView.getInt32(position, true)
628
+ if (value < 0x20000000) {
629
+ if (value > -0x1f000000)
630
+ return value;
631
+ if (value > -0x20000000)
632
+ return toConstant(value & 0xff);
225
633
  }
226
- }
227
- return unpackr.unpack(src.slice(start, end));
228
- case 1: case 2: case 5: case 6:
229
- let fValue = dataView.getFloat32(position, true);
230
- // this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
231
- let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
232
- return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
233
- case 7:
234
- switch((value >> 24) & 0x1f) {
235
- case 0: return null;
236
- case 1: return undefined;
237
- case 2: return false;
238
- case 3: return true;
239
- case 8: return dataView.getFloat64(position + (value & 0x3ffffff), true);
240
- case 0x18: return '';
241
- case 0x19: return String.fromCharCode((value >> 16) & 0xff);
242
- case 0x20: return String.fromCharCode((value >> 16) & 0xff, (value >> 8) & 0xff);
243
- case 0x21: return String.fromCharCode((value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff);
244
- default: throw new Error('Unknown constant');
245
- }
634
+ let fValue = dataView.getFloat32(position, true);
635
+ // this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
636
+ let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
637
+ return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
638
+ };
639
+ break;
640
+ case 8:
641
+ get = function () {
642
+ let source = this[sourceSymbol];
643
+ let src = source.src;
644
+ let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
645
+ let value = dataView.getFloat64(source.position + property.offset, true);
646
+ if (isNaN(value)) {
647
+ let byte = src[source.position + property.offset];
648
+ if (byte >= 0xf6)
649
+ return toConstant(byte);
650
+ }
651
+ return value;
652
+ };
653
+ break;
654
+ case 1:
655
+ get = function () {
656
+ let source = this[sourceSymbol];
657
+ let src = source.src;
658
+ let value = src[source.position + property.offset];
659
+ return value < 0xf6 ? value : toConstant(value);
660
+ };
661
+ break;
246
662
  }
247
- },
248
- enumerable: true,
249
- });
663
+ }
664
+ property.get = get;
250
665
  }
666
+ for (let property of properties) // assign in enumeration order
667
+ Object.defineProperty(prototype, property.key, { get: property.get, enumerable: true });
251
668
  }
252
669
  var instance = new construct();
253
670
  instance[sourceSymbol] = {
254
671
  src,
255
- uint32: src.uint32,
256
672
  position,
673
+ srcString: '',
674
+ srcEnd
257
675
  }
258
676
  return instance;
259
677
  }
260
- setReadStruct(readStruct)
678
+ function toConstant(code) {
679
+ switch(code) {
680
+ case 0xf6: return null;
681
+ case 0xf7: return undefined;
682
+ case 0xf8: return false;
683
+ case 0xf9: return true;
684
+ }
685
+ throw new Error('Unknown constant');
686
+ }
687
+ function prepareStructures(structures, packr) {
688
+ if (!packr.typedStructs)
689
+ return structures;
690
+ let structMap = new Map();
691
+ structMap.set('named', structures);
692
+ structMap.set('typed', packr.typedStructs);
693
+ let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
694
+ structMap.isCompatible = existing => {
695
+ let compatible = true;
696
+ if (existing instanceof Map) {
697
+ let named = existing.get('named') || [];
698
+ if (named.length !== (packr.lastNamedStructuresLength || 0))
699
+ compatible = false;
700
+ let typed = existing.get('typed') || [];
701
+ if (typed.length !== lastTypedStructuresLength)
702
+ compatible = false;
703
+ } else if (existing instanceof Array) {
704
+ if (existing.length !== (packr.lastNamedStructuresLength || 0))
705
+ compatible = false;
706
+ }
707
+ if (!compatible)
708
+ packr._mergeStructures(existing);
709
+ return compatible;
710
+ };
711
+ packr.lastTypedStructuresLength = packr.typedStructs?.length;
712
+ return structMap;
713
+ }
714
+
715
+ setReadStruct(readStruct, onLoadedStructures);
716
+