You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							812 lines
						
					
					
						
							26 KiB
						
					
					
				
			
		
		
	
	
							812 lines
						
					
					
						
							26 KiB
						
					
					
				| 
 | |
| /*
 | |
| 
 | |
| For "any-data":
 | |
| 32-55 - record with record ids (-32)
 | |
| 56 - 8-bit record ids
 | |
| 57 - 16-bit record ids
 | |
| 58 - 24-bit record ids
 | |
| 59 - 32-bit record ids
 | |
| 250-255 - followed by typed fixed width values
 | |
| 64-250 msgpackr/cbor/paired data
 | |
| arrays and strings within arrays are handled by paired encoding
 | |
| 
 | |
| Structure encoding:
 | |
| (type - string (using paired encoding))+
 | |
| 
 | |
| Type encoding
 | |
| encoding byte - fixed width byte - next reference+
 | |
| 
 | |
| Encoding byte:
 | |
| first bit:
 | |
| 	0 - inline
 | |
| 	1 - reference
 | |
| second bit:
 | |
| 	0 - data or number
 | |
| 	1 - string
 | |
| 
 | |
| remaining bits:
 | |
| 	character encoding - ISO-8859-x
 | |
| 
 | |
| 
 | |
| null (0xff)+ 0xf6
 | |
| null (0xff)+ 0xf7
 | |
| 
 | |
| */
 | |
| 
 | |
| 
 | |
| import {setWriteStructSlots, RECORD_SYMBOL, addExtension} from './pack.js'
 | |
| import {setReadStruct, mult10, readString} from './unpack.js';
 | |
| 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)
 | |
| const NUMBER = 0;
 | |
| const UTF8 = 2;
 | |
| const OBJECT_DATA = 1;
 | |
| const DATE = 16;
 | |
| const TYPE_NAMES = ['num', 'object', 'string', 'ascii'];
 | |
| TYPE_NAMES[DATE] = 'date';
 | |
| const float32Headers = [false, true, true, false, false, true, true, false];
 | |
| let evalSupported;
 | |
| try {
 | |
| 	new Function('');
 | |
| 	evalSupported = true;
 | |
| } catch(error) {
 | |
| 	// if eval variants are not supported, do not create inline object readers ever
 | |
| }
 | |
| 
 | |
| let updatedPosition;
 | |
| const hasNodeBuffer = typeof Buffer !== 'undefined'
 | |
| let textEncoder, currentSource;
 | |
| try {
 | |
| 	textEncoder = new TextEncoder()
 | |
| } catch (error) {}
 | |
| const encodeUtf8 = hasNodeBuffer ? function(target, string, position) {
 | |
| 	return target.utf8Write(string, position, 0xffffffff)
 | |
| } : (textEncoder && textEncoder.encodeInto) ?
 | |
| 	function(target, string, position) {
 | |
| 		return textEncoder.encodeInto(string, target.subarray(position)).written
 | |
| 	} : false
 | |
| 
 | |
| const TYPE = Symbol('type');
 | |
| const PARENT = Symbol('parent');
 | |
| setWriteStructSlots(writeStruct, prepareStructures);
 | |
| function writeStruct(object, target, encodingStart, position, structures, makeRoom, pack, packr) {
 | |
| 	let typedStructs = packr.typedStructs || (packr.typedStructs = []);
 | |
| 	// note that we rely on pack.js to load stored structures before we get to this point
 | |
| 	let targetView = target.dataView;
 | |
| 	let refsStartPosition = (typedStructs.lastStringStart || 100) + position;
 | |
| 	let safeEnd = target.length - 10;
 | |
| 	let start = position;
 | |
| 	if (position > safeEnd) {
 | |
| 		target = makeRoom(position);
 | |
| 		targetView = target.dataView;
 | |
| 		position -= encodingStart;
 | |
| 		start -= encodingStart;
 | |
| 		refsStartPosition -= encodingStart;
 | |
| 		encodingStart = 0;
 | |
| 		safeEnd = target.length - 10;
 | |
| 	}
 | |
| 
 | |
| 	let refOffset, refPosition = refsStartPosition;
 | |
| 
 | |
| 	let transition = typedStructs.transitions || (typedStructs.transitions = Object.create(null));
 | |
| 	let nextId = typedStructs.nextId || typedStructs.length;
 | |
| 	let headerSize =
 | |
| 		nextId < 0xf ? 1 :
 | |
| 			nextId < 0xf0 ? 2 :
 | |
| 				nextId < 0xf000 ? 3 :
 | |
| 					nextId < 0xf00000 ? 4 : 0;
 | |
| 	if (headerSize === 0)
 | |
| 		return 0;
 | |
| 	position += headerSize;
 | |
| 	let queuedReferences = [];
 | |
| 	let usedAscii0;
 | |
| 	let keyIndex = 0;
 | |
| 	for (let key in object) {
 | |
| 		let value = object[key];
 | |
| 		let nextTransition = transition[key];
 | |
| 		if (!nextTransition) {
 | |
| 			transition[key] = nextTransition = {
 | |
| 				key,
 | |
| 				parent: transition,
 | |
| 				enumerationOffset: 0,
 | |
| 				ascii0: null,
 | |
| 				ascii8: null,
 | |
| 				num8: null,
 | |
| 				string16: null,
 | |
| 				object16: null,
 | |
| 				num32: null,
 | |
| 				float64: null,
 | |
| 				date64: null
 | |
| 			};
 | |
| 		}
 | |
| 		if (position > safeEnd) {
 | |
| 			target = makeRoom(position);
 | |
| 			targetView = target.dataView;
 | |
| 			position -= encodingStart;
 | |
| 			start -= encodingStart;
 | |
| 			refsStartPosition -= encodingStart;
 | |
| 			refPosition -= encodingStart;
 | |
| 			encodingStart = 0;
 | |
| 			safeEnd = target.length - 10
 | |
| 		}
 | |
| 		switch (typeof value) {
 | |
| 			case 'number':
 | |
| 				let number = value;
 | |
| 				// first check to see if we are using a lot of ids and should default to wide/common format
 | |
| 				if (nextId < 200 || !nextTransition.num64) {
 | |
| 					if (number >> 0 === number && number < 0x20000000 && number > -0x1f000000) {
 | |
| 						if (number < 0xf6 && number >= 0 && (nextTransition.num8 && !(nextId > 200 && nextTransition.num32) || number < 0x20 && !nextTransition.num32)) {
 | |
| 							transition = nextTransition.num8 || createTypeTransition(nextTransition, NUMBER, 1);
 | |
| 							target[position++] = number;
 | |
| 						} else {
 | |
| 							transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
 | |
| 							targetView.setUint32(position, number, true);
 | |
| 							position += 4;
 | |
| 						}
 | |
| 						break;
 | |
| 					} else if (number < 0x100000000 && number >= -0x80000000) {
 | |
| 						targetView.setFloat32(position, number, true);
 | |
| 						if (float32Headers[target[position + 3] >>> 5]) {
 | |
| 							let xShifted
 | |
| 							// this checks for rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
 | |
| 							if (((xShifted = number * mult10[((target[position + 3] & 0x7f) << 1) | (target[position + 2] >> 7)]) >> 0) === xShifted) {
 | |
| 								transition = nextTransition.num32 || createTypeTransition(nextTransition, NUMBER, 4);
 | |
| 								position += 4;
 | |
| 								break;
 | |
| 							}
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				transition = nextTransition.num64 || createTypeTransition(nextTransition, NUMBER, 8);
 | |
| 				targetView.setFloat64(position, number, true);
 | |
| 				position += 8;
 | |
| 				break;
 | |
| 			case 'string':
 | |
| 				let strLength = value.length;
 | |
| 				refOffset = refPosition - refsStartPosition;
 | |
| 				if ((strLength << 2) + refPosition > safeEnd) {
 | |
| 					target = makeRoom((strLength << 2) + refPosition);
 | |
| 					targetView = target.dataView;
 | |
| 					position -= encodingStart;
 | |
| 					start -= encodingStart;
 | |
| 					refsStartPosition -= encodingStart;
 | |
| 					refPosition -= encodingStart;
 | |
| 					encodingStart = 0;
 | |
| 					safeEnd = target.length - 10
 | |
| 				}
 | |
| 				if (strLength > ((0xff00 + refOffset) >> 2)) {
 | |
| 					queuedReferences.push(key, value, position - start);
 | |
| 					break;
 | |
| 				}
 | |
| 				let isNotAscii
 | |
| 				let strStart = refPosition;
 | |
| 				if (strLength < 0x40) {
 | |
| 					let i, c1, c2;
 | |
| 					for (i = 0; i < strLength; i++) {
 | |
| 						c1 = value.charCodeAt(i)
 | |
| 						if (c1 < 0x80) {
 | |
| 							target[refPosition++] = c1
 | |
| 						} else if (c1 < 0x800) {
 | |
| 							isNotAscii = true;
 | |
| 							target[refPosition++] = c1 >> 6 | 0xc0
 | |
| 							target[refPosition++] = c1 & 0x3f | 0x80
 | |
| 						} else if (
 | |
| 							(c1 & 0xfc00) === 0xd800 &&
 | |
| 							((c2 = value.charCodeAt(i + 1)) & 0xfc00) === 0xdc00
 | |
| 						) {
 | |
| 							isNotAscii = true;
 | |
| 							c1 = 0x10000 + ((c1 & 0x03ff) << 10) + (c2 & 0x03ff)
 | |
| 							i++
 | |
| 							target[refPosition++] = c1 >> 18 | 0xf0
 | |
| 							target[refPosition++] = c1 >> 12 & 0x3f | 0x80
 | |
| 							target[refPosition++] = c1 >> 6 & 0x3f | 0x80
 | |
| 							target[refPosition++] = c1 & 0x3f | 0x80
 | |
| 						} else {
 | |
| 							isNotAscii = true;
 | |
| 							target[refPosition++] = c1 >> 12 | 0xe0
 | |
| 							target[refPosition++] = c1 >> 6 & 0x3f | 0x80
 | |
| 							target[refPosition++] = c1 & 0x3f | 0x80
 | |
| 						}
 | |
| 					}
 | |
| 				} else {
 | |
| 					refPosition += encodeUtf8(target, value, refPosition);
 | |
| 					isNotAscii = refPosition - strStart > strLength;
 | |
| 				}
 | |
| 				if (refOffset < 0xa0 || (refOffset < 0xf6 && (nextTransition.ascii8 || nextTransition.string8))) {
 | |
| 					// short strings
 | |
| 					if (isNotAscii) {
 | |
| 						if (!(transition = nextTransition.string8)) {
 | |
| 							if (typedStructs.length > 10 && (transition = nextTransition.ascii8)) {
 | |
| 								// we can safely change ascii to utf8 in place since they are compatible
 | |
| 								transition.__type = UTF8;
 | |
| 								nextTransition.ascii8 = null;
 | |
| 								nextTransition.string8 = transition;
 | |
| 								pack(null, 0, true); // special call to notify that structures have been updated
 | |
| 							} else {
 | |
| 								transition = createTypeTransition(nextTransition, UTF8, 1);
 | |
| 							}
 | |
| 						}
 | |
| 					} else if (refOffset === 0 && !usedAscii0) {
 | |
| 						usedAscii0 = true;
 | |
| 						transition = nextTransition.ascii0 || createTypeTransition(nextTransition, ASCII, 0);
 | |
| 						break; // don't increment position
 | |
| 					}// else ascii:
 | |
| 					else if (!(transition = nextTransition.ascii8) && !(typedStructs.length > 10 && (transition = nextTransition.string8)))
 | |
| 						transition = createTypeTransition(nextTransition, ASCII, 1);
 | |
| 					target[position++] = refOffset;
 | |
| 				} else {
 | |
| 					// TODO: Enable ascii16 at some point, but get the logic right
 | |
| 					//if (isNotAscii)
 | |
| 						transition = nextTransition.string16 || createTypeTransition(nextTransition, UTF8, 2);
 | |
| 					//else
 | |
| 						//transition = nextTransition.ascii16 || createTypeTransition(nextTransition, ASCII, 2);
 | |
| 					targetView.setUint16(position, refOffset, true);
 | |
| 					position += 2;
 | |
| 				}
 | |
| 				break;
 | |
| 			case 'object':
 | |
| 				if (value) {
 | |
| 					if (value.constructor === Date) {
 | |
| 						transition = nextTransition.date64 || createTypeTransition(nextTransition, DATE, 8);
 | |
| 						targetView.setFloat64(position, value.getTime(), true);
 | |
| 						position += 8;
 | |
| 					} else {
 | |
| 						queuedReferences.push(key, value, keyIndex);
 | |
| 					}
 | |
| 					break;
 | |
| 				} else { // null
 | |
| 					nextTransition = anyType(nextTransition, position, targetView, -10); // match CBOR with this
 | |
| 					if (nextTransition) {
 | |
| 						transition = nextTransition;
 | |
| 						position = updatedPosition;
 | |
| 					} else queuedReferences.push(key, value, keyIndex);
 | |
| 				}
 | |
| 				break;
 | |
| 			case 'boolean':
 | |
| 				transition = nextTransition.num8 || nextTransition.ascii8 || createTypeTransition(nextTransition, NUMBER, 1);
 | |
| 				target[position++] = value ? 0xf9 : 0xf8; // match CBOR with these
 | |
| 				break;
 | |
| 			case 'undefined':
 | |
| 				nextTransition = anyType(nextTransition, position, targetView, -9); // match CBOR with this
 | |
| 				if (nextTransition) {
 | |
| 					transition = nextTransition;
 | |
| 					position = updatedPosition;
 | |
| 				} else queuedReferences.push(key, value, keyIndex);
 | |
| 				break;
 | |
| 			default:
 | |
| 				queuedReferences.push(key, value, keyIndex);
 | |
| 		}
 | |
| 		keyIndex++;
 | |
| 	}
 | |
| 
 | |
| 	for (let i = 0, l = queuedReferences.length; i < l;) {
 | |
| 		let key = queuedReferences[i++];
 | |
| 		let value = queuedReferences[i++];
 | |
| 		let propertyIndex = queuedReferences[i++];
 | |
| 		let nextTransition = transition[key];
 | |
| 		if (!nextTransition) {
 | |
| 			transition[key] = nextTransition = {
 | |
| 				key,
 | |
| 				parent: transition,
 | |
| 				enumerationOffset: propertyIndex - keyIndex,
 | |
| 				ascii0: null,
 | |
| 				ascii8: null,
 | |
| 				num8: null,
 | |
| 				string16: null,
 | |
| 				object16: null,
 | |
| 				num32: null,
 | |
| 				float64: null
 | |
| 			};
 | |
| 		}
 | |
| 		let newPosition;
 | |
| 		if (value) {
 | |
| 			/*if (typeof value === 'string') { // TODO: we could re-enable long strings
 | |
| 				if (position + value.length * 3 > safeEnd) {
 | |
| 					target = makeRoom(position + value.length * 3);
 | |
| 					position -= start;
 | |
| 					targetView = target.dataView;
 | |
| 					start = 0;
 | |
| 				}
 | |
| 				newPosition = position + target.utf8Write(value, position, 0xffffffff);
 | |
| 			} else { */
 | |
| 			let size;
 | |
| 			refOffset = refPosition - refsStartPosition;
 | |
| 			if (refOffset < 0xff00) {
 | |
| 				transition = nextTransition.object16;
 | |
| 				if (transition)
 | |
| 					size = 2;
 | |
| 				else if ((transition = nextTransition.object32))
 | |
| 					size = 4;
 | |
| 				else {
 | |
| 					transition = createTypeTransition(nextTransition, OBJECT_DATA, 2);
 | |
| 					size = 2;
 | |
| 				}
 | |
| 			} else {
 | |
| 				transition = nextTransition.object32 || createTypeTransition(nextTransition, OBJECT_DATA, 4);
 | |
| 				size = 4;
 | |
| 			}
 | |
| 			newPosition = pack(value, refPosition);
 | |
| 			//}
 | |
| 			if (typeof newPosition === 'object') {
 | |
| 				// re-allocated
 | |
| 				refPosition = newPosition.position;
 | |
| 				targetView = newPosition.targetView;
 | |
| 				target = newPosition.target;
 | |
| 				refsStartPosition -= encodingStart;
 | |
| 				position -= encodingStart;
 | |
| 				start -= encodingStart;
 | |
| 				encodingStart = 0;
 | |
| 			} else
 | |
| 				refPosition = newPosition;
 | |
| 			if (size === 2) {
 | |
| 				targetView.setUint16(position, refOffset, true);
 | |
| 				position += 2;
 | |
| 			} else {
 | |
| 				targetView.setUint32(position, refOffset, true);
 | |
| 				position += 4;
 | |
| 			}
 | |
| 		} else { // null or undefined
 | |
| 			transition = nextTransition.object16 || createTypeTransition(nextTransition, OBJECT_DATA, 2);
 | |
| 			targetView.setInt16(position, value === null ? -10 : -9, true);
 | |
| 			position += 2;
 | |
| 		}
 | |
| 		keyIndex++;
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	let recordId = transition[RECORD_SYMBOL];
 | |
| 	if (recordId == null) {
 | |
| 		recordId = packr.typedStructs.length;
 | |
| 		let structure = [];
 | |
| 		let nextTransition = transition;
 | |
| 		let key, type;
 | |
| 		while ((type = nextTransition.__type) !== undefined) {
 | |
| 			let size = nextTransition.__size;
 | |
| 			nextTransition = nextTransition.__parent;
 | |
| 			key = nextTransition.key;
 | |
| 			let property = [type, size, key];
 | |
| 			if (nextTransition.enumerationOffset)
 | |
| 				property.push(nextTransition.enumerationOffset);
 | |
| 			structure.push(property);
 | |
| 			nextTransition = nextTransition.parent;
 | |
| 		}
 | |
| 		structure.reverse();
 | |
| 		transition[RECORD_SYMBOL] = recordId;
 | |
| 		packr.typedStructs[recordId] = structure;
 | |
| 		pack(null, 0, true); // special call to notify that structures have been updated
 | |
| 	}
 | |
| 
 | |
| 
 | |
| 	switch (headerSize) {
 | |
| 		case 1:
 | |
| 			if (recordId >= 0x10) return 0;
 | |
| 			target[start] = recordId + 0x20;
 | |
| 			break;
 | |
| 		case 2:
 | |
| 			if (recordId >= 0x100) return 0;
 | |
| 			target[start] = 0x38;
 | |
| 			target[start + 1] = recordId;
 | |
| 			break;
 | |
| 		case 3:
 | |
| 			if (recordId >= 0x10000) return 0;
 | |
| 			target[start] = 0x39;
 | |
| 			targetView.setUint16(start + 1, recordId, true);
 | |
| 			break;
 | |
| 		case 4:
 | |
| 			if (recordId >= 0x1000000) return 0;
 | |
| 			targetView.setUint32(start, (recordId << 8) + 0x3a, true);
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	if (position < refsStartPosition) {
 | |
| 		if (refsStartPosition === refPosition)
 | |
| 			return position; // no refs
 | |
| 		// adjust positioning
 | |
| 		target.copyWithin(position, refsStartPosition, refPosition);
 | |
| 		refPosition += position - refsStartPosition;
 | |
| 		typedStructs.lastStringStart = position - start;
 | |
| 	} else if (position > refsStartPosition) {
 | |
| 		if (refsStartPosition === refPosition)
 | |
| 			return position; // no refs
 | |
| 		typedStructs.lastStringStart = position - start;
 | |
| 		return writeStruct(object, target, encodingStart, start, structures, makeRoom, pack, packr);
 | |
| 	}
 | |
| 	return refPosition;
 | |
| }
 | |
| function anyType(transition, position, targetView, value) {
 | |
| 	let nextTransition;
 | |
| 	if ((nextTransition = transition.ascii8 || transition.num8)) {
 | |
| 		targetView.setInt8(position, value, true);
 | |
| 		updatedPosition = position + 1;
 | |
| 		return nextTransition;
 | |
| 	}
 | |
| 	if ((nextTransition = transition.string16 || transition.object16)) {
 | |
| 		targetView.setInt16(position, value, true);
 | |
| 		updatedPosition = position + 2;
 | |
| 		return nextTransition;
 | |
| 	}
 | |
| 	if (nextTransition = transition.num32) {
 | |
| 		targetView.setUint32(position, 0xe0000100 + value, true);
 | |
| 		updatedPosition = position + 4;
 | |
| 		return nextTransition;
 | |
| 	}
 | |
| 	// transition.float64
 | |
| 	if (nextTransition = transition.num64) {
 | |
| 		targetView.setFloat64(position, NaN, true);
 | |
| 		targetView.setInt8(position, value);
 | |
| 		updatedPosition = position + 8;
 | |
| 		return nextTransition;
 | |
| 	}
 | |
| 	updatedPosition = position;
 | |
| 	// TODO: can we do an "any" type where we defer the decision?
 | |
| 	return;
 | |
| }
 | |
| function createTypeTransition(transition, type, size) {
 | |
| 	let typeName = TYPE_NAMES[type] + (size << 3);
 | |
| 	let newTransition = transition[typeName] || (transition[typeName] = Object.create(null));
 | |
| 	newTransition.__type = type;
 | |
| 	newTransition.__size = size;
 | |
| 	newTransition.__parent = transition;
 | |
| 	return newTransition;
 | |
| }
 | |
| function onLoadedStructures(sharedData) {
 | |
| 	if (!(sharedData instanceof Map))
 | |
| 		return sharedData;
 | |
| 	let typed = sharedData.get('typed') || [];
 | |
| 	if (Object.isFrozen(typed))
 | |
| 		typed = typed.map(structure => structure.slice(0));
 | |
| 	let named = sharedData.get('named');
 | |
| 	let transitions = Object.create(null);
 | |
| 	for (let i = 0, l = typed.length; i < l; i++) {
 | |
| 		let structure = typed[i];
 | |
| 		let transition = transitions;
 | |
| 		for (let [type, size, key] of structure) {
 | |
| 			let nextTransition = transition[key];
 | |
| 			if (!nextTransition) {
 | |
| 				transition[key] = nextTransition = {
 | |
| 					key,
 | |
| 					parent: transition,
 | |
| 					enumerationOffset: 0,
 | |
| 					ascii0: null,
 | |
| 					ascii8: null,
 | |
| 					num8: null,
 | |
| 					string16: null,
 | |
| 					object16: null,
 | |
| 					num32: null,
 | |
| 					float64: null,
 | |
| 					date64: null,
 | |
| 				};
 | |
| 			}
 | |
| 			transition = createTypeTransition(nextTransition, type, size);
 | |
| 		}
 | |
| 		transition[RECORD_SYMBOL] = i;
 | |
| 	}
 | |
| 	typed.transitions = transitions;
 | |
| 	this.typedStructs = typed;
 | |
| 	this.lastTypedStructuresLength = typed.length;
 | |
| 	return named;
 | |
| }
 | |
| var sourceSymbol = Symbol.for('source')
 | |
| function readStruct(src, position, srcEnd, unpackr) {
 | |
| 	let recordId = src[position++] - 0x20;
 | |
| 	if (recordId >= 24) {
 | |
| 		switch(recordId) {
 | |
| 			case 24: recordId = src[position++]; break;
 | |
| 			// little endian:
 | |
| 			case 25: recordId = src[position++] + (src[position++] << 8); break;
 | |
| 			case 26: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16); break;
 | |
| 			case 27: recordId = src[position++] + (src[position++] << 8) + (src[position++] << 16) + (src[position++] << 24); break;
 | |
| 		}
 | |
| 	}
 | |
| 	let structure = unpackr.typedStructs && unpackr.typedStructs[recordId];
 | |
| 	if (!structure) {
 | |
| 		// copy src buffer because getStructures will override it
 | |
| 		src = Uint8Array.prototype.slice.call(src, position, srcEnd);
 | |
| 		srcEnd -= position;
 | |
| 		position = 0;
 | |
| 		if (!unpackr.getStructures)
 | |
| 			throw new Error(`Reference to shared structure ${recordId} without getStructures method`);
 | |
| 		unpackr._mergeStructures(unpackr.getStructures());
 | |
| 		if (!unpackr.typedStructs)
 | |
| 			throw new Error('Could not find any shared typed structures');
 | |
| 		unpackr.lastTypedStructuresLength = unpackr.typedStructs.length;
 | |
| 		structure = unpackr.typedStructs[recordId];
 | |
| 		if (!structure)
 | |
| 			throw new Error('Could not find typed structure ' + recordId);
 | |
| 	}
 | |
| 	var construct = structure.construct;
 | |
| 	if (!construct) {
 | |
| 		construct = structure.construct = function LazyObject() {
 | |
| 		}
 | |
| 		var prototype = construct.prototype;
 | |
| 		let properties = [];
 | |
| 		let currentOffset = 0;
 | |
| 		let lastRefProperty;
 | |
| 		for (let i = 0, l = structure.length; i < l; i++) {
 | |
| 			let definition = structure[i];
 | |
| 			let [ type, size, key, enumerationOffset ] = definition;
 | |
| 			if (key === '__proto__')
 | |
| 				key = '__proto_';
 | |
| 			let property = {
 | |
| 				key,
 | |
| 				offset: currentOffset,
 | |
| 			}
 | |
| 			if (enumerationOffset)
 | |
| 				properties.splice(i + enumerationOffset, 0, property);
 | |
| 			else
 | |
| 				properties.push(property);
 | |
| 			let getRef;
 | |
| 			switch(size) { // TODO: Move into a separate function
 | |
| 				case 0: getRef = () => 0; break;
 | |
| 				case 1:
 | |
| 					getRef = (source, position) => {
 | |
| 						let ref = source.bytes[position + property.offset];
 | |
| 						return ref >= 0xf6 ? toConstant(ref) : ref;
 | |
| 					};
 | |
| 					break;
 | |
| 				case 2:
 | |
| 					getRef = (source, position) => {
 | |
| 						let src = source.bytes;
 | |
| 						let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 						let ref = dataView.getUint16(position + property.offset, true);
 | |
| 						return ref >= 0xff00 ? toConstant(ref & 0xff) : ref;
 | |
| 					};
 | |
| 					break;
 | |
| 				case 4:
 | |
| 					getRef = (source, position) => {
 | |
| 						let src = source.bytes;
 | |
| 						let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 						let ref = dataView.getUint32(position + property.offset, true);
 | |
| 						return ref >= 0xffffff00 ? toConstant(ref & 0xff) : ref;
 | |
| 					};
 | |
| 					break;
 | |
| 			}
 | |
| 			property.getRef = getRef;
 | |
| 			currentOffset += size;
 | |
| 			let get;
 | |
| 			switch(type) {
 | |
| 				case ASCII:
 | |
| 					if (lastRefProperty && !lastRefProperty.next)
 | |
| 						lastRefProperty.next = property;
 | |
| 					lastRefProperty = property;
 | |
| 					property.multiGetCount = 0;
 | |
| 					get = function(source) {
 | |
| 						let src = source.bytes;
 | |
| 						let position = source.position;
 | |
| 						let refStart = currentOffset + position;
 | |
| 						let ref = getRef(source, position);
 | |
| 						if (typeof ref !== 'number') return ref;
 | |
| 
 | |
| 						let end, next = property.next;
 | |
| 						while(next) {
 | |
| 							end = next.getRef(source, position);
 | |
| 							if (typeof end === 'number')
 | |
| 								break;
 | |
| 							else
 | |
| 								end = null;
 | |
| 							next = next.next;
 | |
| 						}
 | |
| 						if (end == null)
 | |
| 							end = source.bytesEnd - refStart;
 | |
| 						if (source.srcString) {
 | |
| 							return source.srcString.slice(ref, end);
 | |
| 						}
 | |
| 						/*if (property.multiGetCount > 0) {
 | |
| 							let asciiEnd;
 | |
| 							next = firstRefProperty;
 | |
| 							let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 							do {
 | |
| 								asciiEnd = dataView.getUint16(source.position + next.offset, true);
 | |
| 								if (asciiEnd < 0xff00)
 | |
| 									break;
 | |
| 								else
 | |
| 									asciiEnd = null;
 | |
| 							} while((next = next.next));
 | |
| 							if (asciiEnd == null)
 | |
| 								asciiEnd = source.bytesEnd - refStart
 | |
| 							source.srcString = src.toString('latin1', refStart, refStart + asciiEnd);
 | |
| 							return source.srcString.slice(ref, end);
 | |
| 						}
 | |
| 						if (source.prevStringGet) {
 | |
| 							source.prevStringGet.multiGetCount += 2;
 | |
| 						} else {
 | |
| 							source.prevStringGet = property;
 | |
| 							property.multiGetCount--;
 | |
| 						}*/
 | |
| 						return readString(src, ref + refStart, end - ref);
 | |
| 						//return src.toString('latin1', ref + refStart, end + refStart);
 | |
| 					};
 | |
| 					break;
 | |
| 				case UTF8: case OBJECT_DATA:
 | |
| 					if (lastRefProperty && !lastRefProperty.next)
 | |
| 						lastRefProperty.next = property;
 | |
| 					lastRefProperty = property;
 | |
| 					get = function(source) {
 | |
| 						let position = source.position;
 | |
| 						let refStart = currentOffset + position;
 | |
| 						let ref = getRef(source, position);
 | |
| 						if (typeof ref !== 'number') return ref;
 | |
| 						let src = source.bytes;
 | |
| 						let end, next = property.next;
 | |
| 						while(next) {
 | |
| 							end = next.getRef(source, position);
 | |
| 							if (typeof end === 'number')
 | |
| 								break;
 | |
| 							else
 | |
| 								end = null;
 | |
| 							next = next.next;
 | |
| 						}
 | |
| 						if (end == null)
 | |
| 							end = source.bytesEnd - refStart;
 | |
| 						if (type === UTF8) {
 | |
| 							return src.toString('utf8', ref + refStart, end + refStart);
 | |
| 						} else {
 | |
| 							currentSource = source;
 | |
| 							try {
 | |
| 								return unpackr.unpack(src, { start: ref + refStart, end: end + refStart });
 | |
| 							} finally {
 | |
| 								currentSource = null;
 | |
| 							}
 | |
| 						}
 | |
| 					};
 | |
| 					break;
 | |
| 				case NUMBER:
 | |
| 					switch(size) {
 | |
| 						case 4:
 | |
| 							get = function (source) {
 | |
| 								let src = source.bytes;
 | |
| 								let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 								let position = source.position + property.offset;
 | |
| 								let value = dataView.getInt32(position, true)
 | |
| 								if (value < 0x20000000) {
 | |
| 									if (value > -0x1f000000)
 | |
| 										return value;
 | |
| 									if (value > -0x20000000)
 | |
| 										return toConstant(value & 0xff);
 | |
| 								}
 | |
| 								let fValue = dataView.getFloat32(position, true);
 | |
| 								// this does rounding of numbers that were encoded in 32-bit float to nearest significant decimal digit that could be preserved
 | |
| 								let multiplier = mult10[((src[position + 3] & 0x7f) << 1) | (src[position + 2] >> 7)]
 | |
| 								return ((multiplier * fValue + (fValue > 0 ? 0.5 : -0.5)) >> 0) / multiplier;
 | |
| 							};
 | |
| 							break;
 | |
| 						case 8:
 | |
| 							get = function (source) {
 | |
| 								let src = source.bytes;
 | |
| 								let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 								let value = dataView.getFloat64(source.position + property.offset, true);
 | |
| 								if (isNaN(value)) {
 | |
| 									let byte = src[source.position + property.offset];
 | |
| 									if (byte >= 0xf6)
 | |
| 										return toConstant(byte);
 | |
| 								}
 | |
| 								return value;
 | |
| 							};
 | |
| 							break;
 | |
| 						case 1:
 | |
| 							get = function (source) {
 | |
| 								let src = source.bytes;
 | |
| 								let value = src[source.position + property.offset];
 | |
| 								return value < 0xf6 ? value : toConstant(value);
 | |
| 							};
 | |
| 							break;
 | |
| 					}
 | |
| 					break;
 | |
| 				case DATE:
 | |
| 					get = function (source) {
 | |
| 						let src = source.bytes;
 | |
| 						let dataView = src.dataView || (src.dataView = new DataView(src.buffer, src.byteOffset, src.byteLength));
 | |
| 						return new Date(dataView.getFloat64(source.position + property.offset, true));
 | |
| 					};
 | |
| 					break;
 | |
| 
 | |
| 			}
 | |
| 			property.get = get;
 | |
| 		}
 | |
| 		// TODO: load the srcString for faster string decoding on toJSON
 | |
| 		if (evalSupported) {
 | |
| 			let objectLiteralProperties = [];
 | |
| 			let args = [];
 | |
| 			let i = 0;
 | |
| 			let hasInheritedProperties;
 | |
| 			for (let property of properties) { // assign in enumeration order
 | |
| 				if (unpackr.alwaysLazyProperty && unpackr.alwaysLazyProperty(property.key)) {
 | |
| 					// these properties are not eagerly evaluated and this can be used for creating properties
 | |
| 					// that are not serialized as JSON
 | |
| 					hasInheritedProperties = true;
 | |
| 					continue;
 | |
| 				}
 | |
| 				Object.defineProperty(prototype, property.key, { get: withSource(property.get), enumerable: true });
 | |
| 				let valueFunction = 'v' + i++;
 | |
| 				args.push(valueFunction);
 | |
| 				objectLiteralProperties.push('[' + JSON.stringify(property.key) + ']:' + valueFunction + '(s)');
 | |
| 			}
 | |
| 			if (hasInheritedProperties) {
 | |
| 				objectLiteralProperties.push('__proto__:this');
 | |
| 			}
 | |
| 			let toObject = (new Function(...args, 'return function(s){return{' + objectLiteralProperties.join(',') + '}}')).apply(null, properties.map(prop => prop.get));
 | |
| 			Object.defineProperty(prototype, 'toJSON', {
 | |
| 				value(omitUnderscoredProperties) {
 | |
| 					return toObject.call(this, this[sourceSymbol]);
 | |
| 				}
 | |
| 			});
 | |
| 		} else {
 | |
| 			Object.defineProperty(prototype, 'toJSON', {
 | |
| 				value(omitUnderscoredProperties) {
 | |
| 					// return an enumerable object with own properties to JSON stringify
 | |
| 					let resolved = {};
 | |
| 					for (let i = 0, l = properties.length; i < l; i++) {
 | |
| 						// TODO: check alwaysLazyProperty
 | |
| 						let key = properties[i].key;
 | |
| 
 | |
| 						resolved[key] = this[key];
 | |
| 					}
 | |
| 					return resolved;
 | |
| 				},
 | |
| 				// not enumerable or anything
 | |
| 			});
 | |
| 		}
 | |
| 	}
 | |
| 	var instance = new construct();
 | |
| 	instance[sourceSymbol] = {
 | |
| 		bytes: src,
 | |
| 		position,
 | |
| 		srcString: '',
 | |
| 		bytesEnd: srcEnd
 | |
| 	}
 | |
| 	return instance;
 | |
| }
 | |
| function toConstant(code) {
 | |
| 	switch(code) {
 | |
| 		case 0xf6: return null;
 | |
| 		case 0xf7: return undefined;
 | |
| 		case 0xf8: return false;
 | |
| 		case 0xf9: return true;
 | |
| 	}
 | |
| 	throw new Error('Unknown constant');
 | |
| }
 | |
| function withSource(get) {
 | |
| 	return function() {
 | |
| 		return get(this[sourceSymbol]);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| function saveState() {
 | |
| 	if (currentSource) {
 | |
| 		currentSource.bytes = Uint8Array.prototype.slice.call(currentSource.bytes, currentSource.position, currentSource.bytesEnd);
 | |
| 		currentSource.position = 0;
 | |
| 		currentSource.bytesEnd = currentSource.bytes.length;
 | |
| 	}
 | |
| }
 | |
| function prepareStructures(structures, packr) {
 | |
| 	if (packr.typedStructs) {
 | |
| 		let structMap = new Map();
 | |
| 		structMap.set('named', structures);
 | |
| 		structMap.set('typed', packr.typedStructs);
 | |
| 		structures = structMap;
 | |
| 	}
 | |
| 	let lastTypedStructuresLength = packr.lastTypedStructuresLength || 0;
 | |
| 	structures.isCompatible = existing => {
 | |
| 		let compatible = true;
 | |
| 		if (existing instanceof Map) {
 | |
| 			let named = existing.get('named') || [];
 | |
| 			if (named.length !== (packr.lastNamedStructuresLength || 0))
 | |
| 				compatible = false;
 | |
| 			let typed = existing.get('typed') || [];
 | |
| 			if (typed.length !== lastTypedStructuresLength)
 | |
| 				compatible = false;
 | |
| 		} else if (existing instanceof Array || Array.isArray(existing)) {
 | |
| 			if (existing.length !== (packr.lastNamedStructuresLength || 0))
 | |
| 				compatible = false;
 | |
| 		}
 | |
| 		if (!compatible)
 | |
| 			packr._mergeStructures(existing);
 | |
| 		return compatible;
 | |
| 	};
 | |
| 	packr.lastTypedStructuresLength = packr.typedStructs && packr.typedStructs.length;
 | |
| 	return structures;
 | |
| }
 | |
| 
 | |
| setReadStruct(readStruct, onLoadedStructures, saveState);
 | |
| 
 |