struct.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  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. if (!construct) {
  498. construct = structure.construct = function LazyObject() {
  499. }
  500. var prototype = construct.prototype;
  501. let properties = [];
  502. let currentOffset = 0;
  503. let lastRefProperty;
  504. for (let i = 0, l = structure.length; i < l; i++) {
  505. let definition = structure[i];
  506. let [ type, size, key, enumerationOffset ] = definition;
  507. if (key === '__proto__')
  508. key = '__proto_';
  509. let property = {
  510. key,
  511. offset: currentOffset,
  512. }
  513. if (enumerationOffset)
  514. properties.splice(i + enumerationOffset, 0, property);
  515. else
  516. properties.push(property);
  517. let getRef;
  518. switch(size) { // TODO: Move into a separate function
  519. case 0: getRef = () => 0; break;
  520. case 1:
  521. getRef = (source, position) => {
  522. let ref = source.bytes[position + property.offset];
  523. return ref >= 0xf6 ? toConstant(ref) : ref;
  524. };
  525. break;
  526. case 2:
  527. getRef = (source, position) => {
  528. let src = source.bytes;
  529. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  530. let ref = dataView.getUint16(position + property.offset, true);
  531. return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
  532. };
  533. break;
  534. case 4:
  535. getRef = (source, position) => {
  536. let src = source.bytes;
  537. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  538. let ref = dataView.getUint32(position + property.offset, true);
  539. return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
  540. };
  541. break;
  542. }
  543. property.getRef = getRef;
  544. currentOffset += size;
  545. let get;
  546. switch(type) {
  547. case ASCII:
  548. if (lastRefProperty && !lastRefProperty.next)
  549. lastRefProperty.next = property;
  550. lastRefProperty = property;
  551. property.multiGetCount = 0;
  552. get = function(source) {
  553. let src = source.bytes;
  554. let position = source.position;
  555. let refStart = currentOffset + position;
  556. let ref = getRef(source, position);
  557. if (typeof ref !== 'number') return ref;
  558. let end, next = property.next;
  559. while(next) {
  560. end = next.getRef(source, position);
  561. if (typeof end === 'number')
  562. break;
  563. else
  564. end = null;
  565. next = next.next;
  566. }
  567. if (end == null)
  568. end = source.bytesEnd - refStart;
  569. if (source.srcString) {
  570. return source.srcString.slice(ref, end);
  571. }
  572. /*if (property.multiGetCount > 0) {
  573. let asciiEnd;
  574. next = firstRefProperty;
  575. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  576. do {
  577. asciiEnd = dataView.getUint16(source.position + next.offset, true);
  578. if (asciiEnd < 0xff00)
  579. break;
  580. else
  581. asciiEnd = null;
  582. } while((next = next.next));
  583. if (asciiEnd == null)
  584. asciiEnd = source.bytesEnd - refStart
  585. source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
  586. return source.srcString.slice(ref, end);
  587. }
  588. if (source.prevStringGet) {
  589. source.prevStringGet.multiGetCount += 2;
  590. } else {
  591. source.prevStringGet = property;
  592. property.multiGetCount--;
  593. }*/
  594. return readString(src, ref + refStart, end - ref);
  595. //return src.toString('latin1', ref + refStart, end + refStart);
  596. };
  597. break;
  598. case UTF8: case OBJECT_DATA:
  599. if (lastRefProperty && !lastRefProperty.next)
  600. lastRefProperty.next = property;
  601. lastRefProperty = property;
  602. get = function(source) {
  603. let position = source.position;
  604. let refStart = currentOffset + position;
  605. let ref = getRef(source, position);
  606. if (typeof ref !== 'number') return ref;
  607. let src = source.bytes;
  608. let end, next = property.next;
  609. while(next) {
  610. end = next.getRef(source, position);
  611. if (typeof end === 'number')
  612. break;
  613. else
  614. end = null;
  615. next = next.next;
  616. }
  617. if (end == null)
  618. end = source.bytesEnd - refStart;
  619. if (type === UTF8) {
  620. return src.toString('utf8', ref + refStart, end + refStart);
  621. } else {
  622. currentSource = source;
  623. try {
  624. return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
  625. } finally {
  626. currentSource = null;
  627. }
  628. }
  629. };
  630. break;
  631. case NUMBER:
  632. switch(size) {
  633. case 4:
  634. get = function (source) {
  635. let src = source.bytes;
  636. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  637. let position = source.position + property.offset;
  638. let value = dataView.getInt32(position, true)
  639. if (value < 0x20000000) {
  640. if (value > -0x1f000000)
  641. return value;
  642. if (value > -0x20000000)
  643. return toConstant(value & 0xff);
  644. }
  645. let fValue = dataView.getFloat32(position, true);
  646. // this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
  647. let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
  648. return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
  649. };
  650. break;
  651. case 8:
  652. get = function (source) {
  653. let src = source.bytes;
  654. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  655. let value = dataView.getFloat64(source.position + property.offset, true);
  656. if (isNaN(value)) {
  657. let byte = src[source.position + property.offset];
  658. if (byte >= 0xf6)
  659. return toConstant(byte);
  660. }
  661. return value;
  662. };
  663. break;
  664. case 1:
  665. get = function (source) {
  666. let src = source.bytes;
  667. let value = src[source.position + property.offset];
  668. return value < 0xf6 ? value : toConstant(value);
  669. };
  670. break;
  671. }
  672. break;
  673. case DATE:
  674. get = function (source) {
  675. let src = source.bytes;
  676. let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
  677. return new Date(dataView.getFloat64(source.position + property.offset, true));
  678. };
  679. break;
  680. }
  681. property.get = get;
  682. }
  683. // TODO: load the srcString for faster string decoding on toJSON
  684. if (evalSupported) {
  685. let objectLiteralProperties = [];
  686. let args = [];
  687. let i = 0;
  688. let hasInheritedProperties;
  689. for (let property of properties) { // assign in enumeration order
  690. if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
  691. // these properties are not eagerly evaluated and this can be used for creating properties
  692. // that are not serialized as JSON
  693. hasInheritedProperties = true;
  694. continue;
  695. }
  696. Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
  697. let valueFunction = 'v' + i++;
  698. args.push(valueFunction);
  699. objectLiteralProperties.push('[' + JSON.stringify(property.key) + ']:' + valueFunction + '(s)');
  700. }
  701. if (hasInheritedProperties) {
  702. objectLiteralProperties.push('__proto__:this');
  703. }
  704. let toObject = (new Function(...args, 'return function(s){return{' + objectLiteralProperties.join(',') + '}}')).apply(null, properties.map(prop => prop.get));
  705. Object.defineProperty(prototype, 'toJSON', {
  706. value(omitUnderscoredProperties) {
  707. return toObject.call(this, this[sourceSymbol]);
  708. }
  709. });
  710. } else {
  711. Object.defineProperty(prototype, 'toJSON', {
  712. value(omitUnderscoredProperties) {
  713. // return an enumerable object with own properties to JSON stringify
  714. let resolved = {};
  715. for (let i = 0, l = properties.length; i < l; i++) {
  716. // TODO: check alwaysLazyProperty
  717. let key = properties[i].key;
  718. resolved[key] = this[key];
  719. }
  720. return resolved;
  721. },
  722. // not enumerable or anything
  723. });
  724. }
  725. }
  726. var instance = new construct();
  727. instance[sourceSymbol] = {
  728. bytes: src,
  729. position,
  730. srcString: '',
  731. bytesEnd: srcEnd
  732. }
  733. return instance;
  734. }
  735. function toConstant(code) {
  736. switch(code) {
  737. case 0xf6: return null;
  738. case 0xf7: return undefined;
  739. case 0xf8: return false;
  740. case 0xf9: return true;
  741. }
  742. throw new Error('Unknown constant');
  743. }
  744. function withSource(get) {
  745. return function() {
  746. return get(this[sourceSymbol]);
  747. }
  748. }
  749. function saveState() {
  750. if (currentSource) {
  751. currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
  752. currentSource.position = 0;
  753. currentSource.bytesEnd = currentSource.bytes.length;
  754. }
  755. }
  756. function prepareStructures(structures, packr) {
  757. if (packr.typedStructs) {
  758. let structMap = new Map();
  759. structMap.set('named', structures);
  760. structMap.set('typed', packr.typedStructs);
  761. structures = structMap;
  762. }
  763. let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
  764. structures.isCompatible = existing => {
  765. let compatible = true;
  766. if (existing instanceof Map) {
  767. let named = existing.get('named') || [];
  768. if (named.length !== (packr.lastNamedStructuresLength || 0))
  769. compatible = false;
  770. let typed = existing.get('typed') || [];
  771. if (typed.length !== lastTypedStructuresLength)
  772. compatible = false;
  773. } else if (existing instanceof Array || Array.isArray(existing)) {
  774. if (existing.length !== (packr.lastNamedStructuresLength || 0))
  775. compatible = false;
  776. }
  777. if (!compatible)
  778. packr._mergeStructures(existing);
  779. return compatible;
  780. };
  781. packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
  782. return structures;
  783. }
  784. setReadStruct(readStruct, onLoadedStructures, saveState);