/** * Copyright 2014 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //import notImplemented = Shumway.Debug.notImplemented; import { assert, IDataDecoder, isNullOrUndefined, ArrayBufferPool, clamp, Errors, IntegerUtilities, utf8decode, utf8encode } from './utilities'; import { Deflate, Inflate } from './deflate'; import { LzmaDecoder } from './lzma'; function axCoerceString(x): string { if (typeof x === 'string') { return x; } else if (x == undefined) { return null; } return x + ''; } export interface IDataInput { readBytes: (bytes: DataBuffer, offset?: number /*uint*/, length?: number /*uint*/) => void; readBoolean: () => boolean; readByte: () => number /*int*/; readUnsignedByte: () => number /*uint*/; readShort: () => number /*int*/; readUnsignedShort: () => number /*uint*/; readInt: () => number /*int*/; readUnsignedInt: () => number /*uint*/; readFloat: () => number; readDouble: () => number; readMultiByte: (length: number /*uint*/, charSet: string) => string; readUTF: () => string; readUTFBytes: (length: number /*uint*/) => string; bytesAvailable: number /*uint*/; objectEncoding: number /*uint*/; endian: string; } export interface IDataOutput { writeBytes: (bytes: DataBuffer, offset?: number /*uint*/, length?: number /*uint*/) => void; writeBoolean: (value: boolean) => void; writeByte: (value: number /*int*/) => void; writeShort: (value: number /*int*/) => void; writeInt: (value: number /*int*/) => void; writeUnsignedInt: (value: number /*uint*/) => void; writeFloat: (value: number) => void; writeDouble: (value: number) => void; writeMultiByte: (value: string, charSet: string) => void; writeUTF: (value: string) => void; writeUTFBytes: (value: string) => void; objectEncoding: number /*uint*/; endian: string; } export class PlainObjectDataBuffer { constructor(public buffer: ArrayBuffer, public length: number, public littleEndian: boolean) { } } const bitMasks = new Uint32Array(33); for (let i = 1, mask = 0; i <= 32; i++) { bitMasks[i] = mask = (mask << 1) | 1; } enum TypedArrayViewFlags { U8 = 1, I32 = 2, F32 = 4 } export class DataBuffer implements IDataInput, IDataOutput { private static _nativeLittleEndian = new Int8Array(new Int32Array([1]).buffer)[0] === 1; /* The initial size of the backing, in bytes. Doubled every OOM. */ private static INITIAL_SIZE = 128; private _buffer: ArrayBuffer; private _length: number; private _position: number; private _littleEndian: boolean; private _objectEncoding: number; private _u8: Uint8Array; private _i32: Int32Array; private _f32: Float32Array; private _bitBuffer: number; private _bitLength: number; private static _arrayBufferPool = new ArrayBufferPool(); constructor(initialSize: number = DataBuffer.INITIAL_SIZE) { // If we're constructing a child class of DataBuffer (or ByteArray), buffer initialization // has already happened at this point. if (this._buffer) { return; } this._buffer = new ArrayBuffer(initialSize); this._length = 0; this._position = 0; this._resetViews(); this._littleEndian = DataBuffer._nativeLittleEndian; this._bitBuffer = 0; this._bitLength = 0; } static FromArrayBuffer(buffer: ArrayBuffer, length: number = -1): DataBuffer { const dataBuffer: DataBuffer = Object.create(DataBuffer.prototype); dataBuffer._buffer = buffer; dataBuffer._length = length === -1 ? buffer.byteLength : length; dataBuffer._position = 0; dataBuffer._resetViews(); dataBuffer._littleEndian = DataBuffer._nativeLittleEndian; dataBuffer._bitBuffer = 0; dataBuffer._bitLength = 0; return dataBuffer; } static FromPlainObject(source: PlainObjectDataBuffer): DataBuffer { const dataBuffer = DataBuffer.FromArrayBuffer(source.buffer, source.length); dataBuffer._littleEndian = source.littleEndian; return dataBuffer; } toPlainObject(): PlainObjectDataBuffer { return new PlainObjectDataBuffer(this._buffer, this._length, this._littleEndian); } /** * Clone the DataBuffer in a way that guarantees the underlying ArrayBuffer to be copied * into an instance of the current global's ArrayBuffer. * * Important if the underlying buffer comes from a different global, in which case accessing * its elements is excruiciatingly slow. */ clone(): DataBuffer { const clone = DataBuffer.FromArrayBuffer(new Uint8Array(this._u8).buffer, this._length); clone._position = this._position; clone._littleEndian = this._littleEndian; clone._bitBuffer = this._bitBuffer; clone._bitLength = this._bitLength; return clone; } /** * By default, we only have a byte view. All other views are |null|. */ private _resetViews() { this._u8 = new Uint8Array(this._buffer); this._i32 = null; this._f32 = null; } /** * We don't want to eagerly allocate views if we won't ever need them. You must call this method * before using a view of a certain type to make sure it's available. Once a view is allocated, * it is not re-allocated unless the view becomes |null| as a result of a call to |resetViews|. */ private _requestViews(flags: TypedArrayViewFlags) { if ((this._buffer.byteLength & 0x3) === 0) { if (this._i32 === null && flags & TypedArrayViewFlags.I32) { this._i32 = new Int32Array(this._buffer); } if (this._f32 === null && flags & TypedArrayViewFlags.F32) { this._f32 = new Float32Array(this._buffer); } } } getBytes(): Uint8Array { return new Uint8Array(this._buffer, 0, this._length); } private _ensureCapacity(length: number) { const currentBuffer = this._buffer; if (currentBuffer.byteLength >= length) { return; } let newLength = Math.max(currentBuffer.byteLength, 1); while (newLength < length) { newLength *= 2; } if (newLength > 0xFFFFFFFF) { assert(( this).sec); ( this).sec.throwError('RangeError', Errors.ParamRangeError); } const newBuffer = DataBuffer._arrayBufferPool.acquire(newLength); const curentView = this._u8; this._buffer = newBuffer; this._resetViews(); this._u8.set(curentView); const u8 = this._u8; // Zero out the rest of the buffer, since the arrayBufferPool doesn't // always give us a empty buffer. for (let i = curentView.length; i < u8.length; i++) { u8[i] = 0; } DataBuffer._arrayBufferPool.release(currentBuffer); } clear() { this._length = 0; this._position = 0; } readBoolean(): boolean { return this.readUnsignedByte() !== 0; } readByte(): number /*int*/ { return this.readUnsignedByte() << 24 >> 24; } readUnsignedByte(): number /*uint*/ { if (this._position + 1 > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } return this._u8[this._position++]; } readBytes(bytes: DataBuffer, offset?: number /*uint*/, length?: number /*uint*/): void { const position = this._position; offset = offset >>> 0; length = length >>> 0; if (length === 0) { length = this._length - position; } if (position + length > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } if (bytes.length < offset + length) { bytes._ensureCapacity(offset + length); bytes.length = offset + length; } bytes._u8.set(new Uint8Array(this._buffer, position, length), offset); this._position += length; } readShort(): number /*int*/ { return this.readUnsignedShort() << 16 >> 16; } readUnsignedShort(): number /*uint*/ { const u8 = this._u8; const position = this._position; if (position + 2 > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } const a = u8[position + 0]; const b = u8[position + 1]; this._position = position + 2; return this._littleEndian ? (b << 8) | a : (a << 8) | b; } readInt(): number /*int*/ { const u8 = this._u8; const position = this._position; if (position + 4 > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } const a = u8[position + 0]; const b = u8[position + 1]; const c = u8[position + 2]; const d = u8[position + 3]; this._position = position + 4; return this._littleEndian ? (d << 24) | (c << 16) | (b << 8) | a : (a << 24) | (b << 16) | (c << 8) | d; } readUnsignedInt(): number /*uint*/ { return this.readInt() >>> 0; } readFloat(): number { const position = this._position; if (position + 4 > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } this._position = position + 4; this._requestViews(TypedArrayViewFlags.F32); if (this._littleEndian && (position & 0x3) === 0 && this._f32) { return this._f32[position >> 2]; } else { const u8 = this._u8; const t8 = IntegerUtilities.u8; if (this._littleEndian) { t8[0] = u8[position + 0]; t8[1] = u8[position + 1]; t8[2] = u8[position + 2]; t8[3] = u8[position + 3]; } else { t8[3] = u8[position + 0]; t8[2] = u8[position + 1]; t8[1] = u8[position + 2]; t8[0] = u8[position + 3]; } return IntegerUtilities.f32[0]; } } readDouble(): number { const u8 = this._u8; const position = this._position; if (position + 8 > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } const t8 = IntegerUtilities.u8; if (this._littleEndian) { t8[0] = u8[position + 0]; t8[1] = u8[position + 1]; t8[2] = u8[position + 2]; t8[3] = u8[position + 3]; t8[4] = u8[position + 4]; t8[5] = u8[position + 5]; t8[6] = u8[position + 6]; t8[7] = u8[position + 7]; } else { t8[0] = u8[position + 7]; t8[1] = u8[position + 6]; t8[2] = u8[position + 5]; t8[3] = u8[position + 4]; t8[4] = u8[position + 3]; t8[5] = u8[position + 2]; t8[6] = u8[position + 1]; t8[7] = u8[position + 0]; } this._position = position + 8; return IntegerUtilities.f64[0]; } writeBoolean(value: boolean): void { this.writeByte(value ? 1 : 0); } writeByte(value: number /*int*/): void { const length = this._position + 1; this._ensureCapacity(length); this._u8[this._position++] = value; if (length > this._length) { this._length = length; } } writeUnsignedByte(value: number /*uint*/): void { const length = this._position + 1; this._ensureCapacity(length); this._u8[this._position++] = value; if (length > this._length) { this._length = length; } } writeRawBytes(bytes: Uint8Array): void { const length = this._position + bytes.length; this._ensureCapacity(length); this._u8.set(bytes, this._position); this._position = length; if (length > this._length) { this._length = length; } } writeBytes(bytes: DataBuffer, offset?: number /*uint*/, length?: number /*uint*/): void { if (isNullOrUndefined(bytes)) { assert(( this).sec); ( this).sec.throwError('TypeError', Errors.NullPointerError, 'bytes'); } offset = offset >>> 0; length = length >>> 0; if (arguments.length < 2) { offset = 0; } if (arguments.length < 3) { length = 0; } if (offset !== clamp(offset, 0, bytes.length) || offset + length !== clamp(offset + length, 0, bytes.length)) { assert(( this).sec); ( this).sec.throwError('RangeError', Errors.ParamRangeError); } if (length === 0) { length = bytes.length - offset; } this.writeRawBytes(new Uint8Array(bytes._buffer, offset, length)); } writeShort(value: number /*int*/): void { this.writeUnsignedShort(value); } writeUnsignedShort(value: number /*uint*/): void { let position = this._position; this._ensureCapacity(position + 2); const u8 = this._u8; if (this._littleEndian) { u8[position + 0] = value; u8[position + 1] = value >> 8; } else { u8[position + 0] = value >> 8; u8[position + 1] = value; } position += 2; this._position = position; if (position > this._length) { this._length = position; } } writeInt(value: number /*int*/): void { this.writeUnsignedInt(value); } write2Ints(a: number, b: number): void { this.write2UnsignedInts(a, b); } write4Ints(a: number, b: number, c: number, d: number): void { this.write4UnsignedInts(a, b, c, d); } writeUnsignedInt(value: number /*uint*/): void { let position = this._position; this._ensureCapacity(position + 4); this._requestViews(TypedArrayViewFlags.I32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._i32) { this._i32[position >> 2] = value; } else { const u8 = this._u8; if (this._littleEndian) { u8[position + 0] = value; u8[position + 1] = value >> 8; u8[position + 2] = value >> 16; u8[position + 3] = value >> 24; } else { u8[position + 0] = value >> 24; u8[position + 1] = value >> 16; u8[position + 2] = value >> 8; u8[position + 3] = value; } } position += 4; this._position = position; if (position > this._length) { this._length = position; } } write2UnsignedInts(a: number, b: number): void { let position = this._position; this._ensureCapacity(position + 8); this._requestViews(TypedArrayViewFlags.I32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._i32) { this._i32[(position >> 2) + 0] = a; this._i32[(position >> 2) + 1] = b; position += 8; this._position = position; if (position > this._length) { this._length = position; } } else { this.writeUnsignedInt(a); this.writeUnsignedInt(b); } } write4UnsignedInts(a: number, b: number, c: number, d: number): void { let position = this._position; this._ensureCapacity(position + 16); this._requestViews(TypedArrayViewFlags.I32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._i32) { this._i32[(position >> 2) + 0] = a; this._i32[(position >> 2) + 1] = b; this._i32[(position >> 2) + 2] = c; this._i32[(position >> 2) + 3] = d; position += 16; this._position = position; if (position > this._length) { this._length = position; } } else { this.writeUnsignedInt(a); this.writeUnsignedInt(b); this.writeUnsignedInt(c); this.writeUnsignedInt(d); } } writeFloat(value: number): void { let position = this._position; this._ensureCapacity(position + 4); this._requestViews(TypedArrayViewFlags.F32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._f32) { this._f32[position >> 2] = value; } else { const u8 = this._u8; IntegerUtilities.f32[0] = value; const t8 = IntegerUtilities.u8; if (this._littleEndian) { u8[position + 0] = t8[0]; u8[position + 1] = t8[1]; u8[position + 2] = t8[2]; u8[position + 3] = t8[3]; } else { u8[position + 0] = t8[3]; u8[position + 1] = t8[2]; u8[position + 2] = t8[1]; u8[position + 3] = t8[0]; } } position += 4; this._position = position; if (position > this._length) { this._length = position; } } write2Floats(a: number, b: number): void { let position = this._position; this._ensureCapacity(position + 8); this._requestViews(TypedArrayViewFlags.F32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._f32) { this._f32[(position >> 2) + 0] = a; this._f32[(position >> 2) + 1] = b; position += 8; this._position = position; if (position > this._length) { this._length = position; } } else { this.writeFloat(a); this.writeFloat(b); } } write6Floats(a: number, b: number, c: number, d: number, e: number, f: number): void { let position = this._position; this._ensureCapacity(position + 24); this._requestViews(TypedArrayViewFlags.F32); if (this._littleEndian === DataBuffer._nativeLittleEndian && (position & 0x3) === 0 && this._f32) { this._f32[(position >> 2) + 0] = a; this._f32[(position >> 2) + 1] = b; this._f32[(position >> 2) + 2] = c; this._f32[(position >> 2) + 3] = d; this._f32[(position >> 2) + 4] = e; this._f32[(position >> 2) + 5] = f; position += 24; this._position = position; if (position > this._length) { this._length = position; } } else { this.writeFloat(a); this.writeFloat(b); this.writeFloat(c); this.writeFloat(d); this.writeFloat(e); this.writeFloat(f); } } writeDouble(value: number): void { let position = this._position; this._ensureCapacity(position + 8); const u8 = this._u8; IntegerUtilities.f64[0] = value; const t8 = IntegerUtilities.u8; if (this._littleEndian) { u8[position + 0] = t8[0]; u8[position + 1] = t8[1]; u8[position + 2] = t8[2]; u8[position + 3] = t8[3]; u8[position + 4] = t8[4]; u8[position + 5] = t8[5]; u8[position + 6] = t8[6]; u8[position + 7] = t8[7]; } else { u8[position + 0] = t8[7]; u8[position + 1] = t8[6]; u8[position + 2] = t8[5]; u8[position + 3] = t8[4]; u8[position + 4] = t8[3]; u8[position + 5] = t8[2]; u8[position + 6] = t8[1]; u8[position + 7] = t8[0]; } position += 8; this._position = position; if (position > this._length) { this._length = position; } } readRawBytes(): Int8Array { return new Int8Array(this._buffer, 0, this._length); } writeUTF(value: string): void { value = axCoerceString(value); const bytes = utf8decode(value); this.writeShort(bytes.length); this.writeRawBytes(bytes); } writeUTFBytes(value: string): void { value = axCoerceString(value); const bytes = utf8decode(value); this.writeRawBytes(bytes); } readUTF(): string { return this.readUTFBytes(this.readShort()); } readUTFBytes(length: number /*uint*/): string { length = length >>> 0; const pos = this._position; if (pos + length > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } this._position += length; return utf8encode(new Uint8Array(this._buffer, pos, length)); } get length(): number /*uint*/ { return this._length; } set length(value: number /*uint*/) { value = value >>> 0; const capacity = this._buffer.byteLength; /* XXX: Do we need to zero the difference if length <= cap? */ if (value > capacity) { this._ensureCapacity(value); } this._length = value; this._position = clamp(this._position, 0, this._length); } get bytesAvailable(): number /*uint*/ { return this._length - this._position; } get position(): number /*uint*/ { return this._position; } get buffer(): ArrayBuffer { return this._buffer; } get bytes(): Uint8Array { return this._u8; } get ints(): Int32Array { this._requestViews(TypedArrayViewFlags.I32); return this._i32; } set position(position: number /*uint*/) { this._position = position >>> 0; } get objectEncoding(): number /*uint*/ { return this._objectEncoding; } set objectEncoding(version: number /*uint*/) { version = version >>> 0; this._objectEncoding = version; } get endian(): string { return this._littleEndian ? 'littleEndian' : 'bigEndian'; } set endian(type: string) { type = axCoerceString(type); if (type === 'auto') { this._littleEndian = DataBuffer._nativeLittleEndian; } else { this._littleEndian = type === 'littleEndian'; } } toString(): string { return utf8encode(new Uint8Array(this._buffer, 0, this._length)); } toBlob(type: string): Blob { return new Blob([new Uint8Array(this._buffer, this._position, this._length)], { type: type }); } writeMultiByte(value: string, charSet: string): void { value = axCoerceString(value); charSet = axCoerceString(charSet); if (charSet == 'UTF-8') { const bytes = utf8decode(value); this.writeRawBytes(bytes); } else { console.log('packageInternal flash.utils.ObjectOutput::writeMultiByte only encoding supported is UTF-8'); } } readMultiByte(length: number /*uint*/, charSet: string): string { length = length >>> 0; charSet = axCoerceString(charSet); console.log('packageInternal flash.utils.ObjectInput::readMultiByte'); return; } getValue(name: number): any { name = name | 0; if (name >= this._length) { return undefined; } return this._u8[name]; } setValue(name: number, value: any) { name = name | 0; const length = name + 1; this._ensureCapacity(length); this._u8[name] = value; if (length > this._length) { this._length = length; } } readFixed(): number { return this.readInt() / 65536; } readFixed8(): number { return this.readShort() / 256; } readFloat16(): number { const uint16 = this.readUnsignedShort(); const sign = uint16 >> 15 ? -1 : 1; const exponent = (uint16 & 0x7c00) >> 10; const fraction = uint16 & 0x03ff; if (!exponent) { return sign * Math.pow(2, -14) * (fraction / 1024); } if (exponent === 0x1f) { return fraction ? NaN : sign * Infinity; } return sign * Math.pow(2, exponent - 15) * (1 + (fraction / 1024)); } readEncodedU32(): number { let value = this.readUnsignedByte(); if (!(value & 0x080)) { return value; } value = (value & 0x7f) | this.readUnsignedByte() << 7; if (!(value & 0x4000)) { return value; } value = (value & 0x3fff) | this.readUnsignedByte() << 14; if (!(value & 0x200000)) { return value; } value = (value & 0x1FFFFF) | this.readUnsignedByte() << 21; if (!(value & 0x10000000)) { return value; } return (value & 0xFFFFFFF) | (this.readUnsignedByte() << 28); } readBits(size: number): number { return (this.readUnsignedBits(size) << (32 - size)) >> (32 - size); } readUnsignedBits(size: number): number { let buffer = this._bitBuffer; let length = this._bitLength; while (size > length) { buffer = (buffer << 8) | this.readUnsignedByte(); length += 8; } length -= size; const value = (buffer >>> length) & bitMasks[size]; this._bitBuffer = buffer; this._bitLength = length; return value; } readFixedBits(size: number): number { return this.readBits(size) / 65536; } readString(length?: number): string { const position = this._position; if (length) { if (position + length > this._length) { assert(( this).sec); ( this).sec.throwError('flash.errors.EOFError', Errors.EOFError); } this._position += length; } else { length = 0; for (let i = position; i < this._length && this._u8[i]; i++) { length++; } this._position += length + 1; } return utf8encode(new Uint8Array(this._buffer, position, length)); } align() { this._bitBuffer = 0; this._bitLength = 0; } deflate() { this.compress('deflate'); } inflate() { this.uncompress('deflate'); } compress(algorithm: string): void { if (arguments.length === 0) { algorithm = 'zlib'; } else { algorithm = axCoerceString(algorithm); } let deflate: Deflate; switch (algorithm) { case 'zlib': deflate = new Deflate(true); break; case 'deflate': deflate = new Deflate(false); break; default: return; } const output = new DataBuffer(); deflate.onData = output.writeRawBytes.bind(output); deflate.push(this._u8.subarray(0, this._length)); deflate.close(); this._ensureCapacity(output._u8.length); this._u8.set(output._u8); this.length = output.length; this._position = 0; } uncompress(algorithm: string): void { if (arguments.length === 0) { algorithm = 'zlib'; } else { algorithm = axCoerceString(algorithm); } let inflate: IDataDecoder; switch (algorithm) { case 'zlib': inflate = Inflate.create(true); break; case 'deflate': inflate = Inflate.create(false); break; case 'lzma': inflate = new LzmaDecoder(false); break; default: return; } const output = new DataBuffer(); let error; inflate.onData = output.writeRawBytes.bind(output); inflate.onError = (e) => error = e; inflate.push(this._u8.subarray(0, this._length)); if (error) { assert(( this).sec); ( this).sec.throwError('IOError', Errors.CompressedDataError); } inflate.close(); this._ensureCapacity(output._u8.length); this._u8.set(output._u8); this.length = output.length; this._position = 0; } }