import { Stream } from './stream'; import { Mapping } from '../utils/mapping'; import { Reference } from '../utils/reference'; import { Markers } from '../enums/markers'; import { ECMAArray } from '../index'; /** * @exports * @class */ export class Deserializer { /** * @private * @description The stream holder * @type {Stream} */ private stream: Stream; /** * @private * @description The mapping holder * @type {Mapping} */ private mapping: Mapping; /** * @private * @description The reference holder * @type {Reference} */ private reference: Reference; /** * @constructor * @param {object} options */ constructor(options: { [key: string]: Stream | Mapping; }) { /** * @description Initialize the stream holder * @type {Stream} */ this.stream = options.stream as Stream; /** * @description Initialize the mapping holder * @type {Mapping} */ this.mapping = options.mapping as Mapping; /** * @description Initialize the reference holder * @type {Reference} */ this.reference = new Reference(); } /** * @public * @description Deserializes AMF0 bytes to data * @returns {any} */ public deserialize(): any { const marker: number = this.stream.readUnsignedByte(); switch (marker) { case Markers.NULL: return this.deserializeNull(); case Markers.UNDEFINED: return this.deserializeUndefined(); case Markers.BOOLEAN: return this.deserializeBoolean(); case Markers.NUMBER: return this.deserializeNumber(); case Markers.STRING: return this.deserializeString(); case Markers.LONG_STRING: return this.deserializeLongString(); case Markers.REFERENCE: return this.deserializeReference(); case Markers.OBJECT: return this.deserializeObject(); case Markers.ECMA_ARRAY: return this.deserializeECMAArray(); case Markers.DATE: return this.deserializeDate(); case Markers.MAP: return this.deserializeMap(); case Markers.SET: return this.deserializeSet(); case Markers.TYPED_OBJECT: return this.deserializeTypedObject(); case Markers.UNSUPPORTED: return this.deserializeUnsupported(); case Markers.OBJECT_END: throw new RangeError(`Unexpected object end marker found on position: '${this.stream.position}'.`); default: throw new TypeError(`Unknown or unsupported marker found: '${marker}'.`); } } /** * @private * @description Deserializes a null * @returns {null} */ private deserializeNull(): null { return null; } /** * @private * @description Deserializes an undefined * @returns {undefined} */ private deserializeUndefined(): undefined { return undefined; } /** * @private * @description Deserializes a boolean * @returns {boolean} */ private deserializeBoolean(): boolean { return this.stream.readBoolean(); } /** * @private * @description Deserializes a number * @returns {number} */ private deserializeNumber(): number { return this.stream.readDouble(); } /** * @private * @description Deserializes a string * @returns {string} */ private deserializeString(): string { return this.stream.readUTF(); } /** * @private * @description Deserializes a long string * @returns {string} */ private deserializeLongString(): string { return this.stream.readUTFBytes(this.stream.readUnsignedInt()); } /** * @private * @description Deserializes a reference * @returns {object} */ private deserializeReference(): object { return this.reference.get(this.stream.readUnsignedShort()); } /** * @private * @description Deserializes an object end * @param {boolean} emptyString * @returns {boolean} */ private deserializeObjectEnd(emptyString: boolean): boolean { // Objects already read UTF-8-empty const hasEmptyString: boolean = emptyString ? this.stream.readShort() === 0 : true; const hasObjectEndByte: boolean = this.stream.readUnsignedByte() === Markers.OBJECT_END; return (hasEmptyString && hasObjectEndByte); } /** * @private * @description Deserializes an object * @returns {object} */ private deserializeObject(): object { const value: { [key: string]: any; } = {}; this.reference.add(value); for (let key: string = this.stream.readUTF(); key !== ''; key = this.stream.readUTF()) { value[key] = this.deserialize(); } const hasObjectEnd: boolean = this.deserializeObjectEnd(false); if (!hasObjectEnd) { throw new RangeError(`Couldn't read all data.`); } return value; } /** * @private * @description Deserializes an ECMA array * @returns {ECMAArray} */ private deserializeECMAArray(): ECMAArray { const value: ECMAArray = []; const length: number = this.stream.readUnsignedInt(); value.length = length; this.reference.add(value); for (let i: number = 0; i < length; i++) { const key: string = this.stream.readUTF(); const deserialized: any = this.deserialize(); if (deserialized !== undefined && deserialized !== null) { value[key] = deserialized; } } const hasObjectEnd: boolean = this.deserializeObjectEnd(true); if (!hasObjectEnd) { throw new RangeError(`Couldn't read all data.`); } return value; } /** * @private * @description Deserializes a date * @returns {Date} */ private deserializeDate(): Date { const value: Date = new Date(this.stream.readDouble()); this.reference.add(value); this.stream.position += 2; // Reserved timezone offset return value; } /** * @private * @description Deserializes a map * @returns {Map} */ private deserializeMap(): Map { const value: Map = new Map(); this.reference.add(value); for (let key: string = this.stream.readUTF(); key !== ''; key = this.stream.readUTF()) { value.set(key, this.deserialize()); } const hasObjectEnd: boolean = this.deserializeObjectEnd(false); if (!hasObjectEnd) { throw new RangeError(`Couldn't read all data.`); } return value; } /** * @private * @description Deserializes a set * @returns {Set} */ private deserializeSet(): Set { const value: Set = new Set(); const length: number = this.stream.readUnsignedInt(); this.reference.add(value); for (let i: number = 0; i < length; i++) { value.add(this.deserialize()); } const hasObjectEnd: boolean = this.deserializeObjectEnd(true); if (!hasObjectEnd) { throw new RangeError(`Couldn't read all data.`); } return value; } /** * @private * @description Deserializes a typed object * @returns {object} */ private deserializeTypedObject(): object { let value: { [key: string]: any; } = {}; const aliasName: string = this.stream.readUTF(); const classObject: any = this.mapping.getDefinitionByName(aliasName); if (!classObject) { throw new TypeError(`The class '${aliasName}' is not registered.`); } value = new classObject(); this.reference.add(value); for (let key: string = this.stream.readUTF(); key !== ''; key = this.stream.readUTF()) { value[key] = this.deserialize(); } const hasObjectEnd: boolean = this.deserializeObjectEnd(false); if (!hasObjectEnd) { throw new RangeError(`Couldn't read all data.`); } return value; } /** * @private * @description Deserializes an unsupported * @returns {void} */ private deserializeUnsupported(): void { // Nothing to do here // The spec says that implementations can throw an error // We won't do this } }