struct.js 27 KB

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