1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/workerlz4/lz4.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,136 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +if (typeof Components != "undefined") { 1.11 + throw new Error("This file is meant to be loaded in a worker"); 1.12 +} 1.13 +if (!module || !exports) { 1.14 + throw new Error("Please load this module with require()"); 1.15 +} 1.16 + 1.17 +const SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); 1.18 +const Internals = require("resource://gre/modules/workers/lz4_internal.js"); 1.19 + 1.20 +const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0" 1.21 + 1.22 +const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size; 1.23 + 1.24 +const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER; 1.25 + 1.26 +const EXPECTED_HEADER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, HEADER_SIZE); 1.27 +const EXPECTED_SIZE_BUFFER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, BYTES_IN_SIZE_HEADER); 1.28 + 1.29 +/** 1.30 + * An error during (de)compression 1.31 + * 1.32 + * @param {string} operation The name of the operation ("compress", "decompress") 1.33 + * @param {string} reason A reason to be used when matching errors. Must start 1.34 + * with "because", e.g. "becauseInvalidContent". 1.35 + * @param {string} message A human-readable message. 1.36 + */ 1.37 +function LZError(operation, reason, message) { 1.38 + SharedAll.OSError.call(this); 1.39 + this.operation = operation; 1.40 + this[reason] = true; 1.41 + this.message = message; 1.42 +} 1.43 +LZError.prototype = Object.create(SharedAll.OSError); 1.44 +LZError.prototype.toString = function toString() { 1.45 + return this.message; 1.46 +}; 1.47 +exports.Error = LZError; 1.48 + 1.49 +/** 1.50 + * Compress a block to a form suitable for writing to disk. 1.51 + * 1.52 + * Compatibility note: For the moment, we are basing our code on lz4 1.53 + * 1.3, which does not specify a *file* format. Therefore, we define 1.54 + * our own format. Once lz4 defines a complete file format, we will 1.55 + * migrate both |compressFileContent| and |decompressFileContent| to this file 1.56 + * format. For backwards-compatibility, |decompressFileContent| will however 1.57 + * keep the ability to decompress files provided with older versions of 1.58 + * |compressFileContent|. 1.59 + * 1.60 + * Compressed files have the following layout: 1.61 + * 1.62 + * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress | 1.63 + * 1.64 + * @param {TypedArray|void*} buffer The buffer to write to the disk. 1.65 + * @param {object=} options An object that may contain the following fields: 1.66 + * - {number} bytes The number of bytes to read from |buffer|. If |buffer| 1.67 + * is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If 1.68 + * |buffer| is a |void*|, |bytes| MUST be provided. 1.69 + * @return {Uint8Array} An array of bytes suitable for being written to the 1.70 + * disk. 1.71 + */ 1.72 +function compressFileContent(array, options = {}) { 1.73 + // Prepare the output array 1.74 + let inputBytes; 1.75 + if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) { 1.76 + inputBytes = array.byteLength; 1.77 + } else if (options && options.bytes) { 1.78 + inputBytes = options.bytes; 1.79 + } else { 1.80 + throw new TypeError("compressFileContent requires a size"); 1.81 + } 1.82 + let maxCompressedSize = Internals.maxCompressedSize(inputBytes); 1.83 + let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize); 1.84 + 1.85 + // Compress to output array 1.86 + let payload = new Uint8Array(outputArray.buffer, outputArray.byteOffset + HEADER_SIZE); 1.87 + let compressedSize = Internals.compress(array, inputBytes, payload); 1.88 + 1.89 + // Add headers 1.90 + outputArray.set(MAGIC_NUMBER); 1.91 + let view = new DataView(outputArray.buffer); 1.92 + view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true); 1.93 + 1.94 + return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize); 1.95 +} 1.96 +exports.compressFileContent = compressFileContent; 1.97 + 1.98 +function decompressFileContent(array, options = {}) { 1.99 + let {ptr, bytes} = SharedAll.normalizeToPointer(array, options.bytes || null); 1.100 + if (bytes < HEADER_SIZE) { 1.101 + throw new LZError("decompress", "becauseLZNoHeader", "Buffer is too short (no header)"); 1.102 + } 1.103 + 1.104 + // Read headers 1.105 + let expectMagicNumber = ctypes.cast(ptr, EXPECTED_HEADER_TYPE.ptr).contents; 1.106 + for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) { 1.107 + if (expectMagicNumber[i] != MAGIC_NUMBER[i]) { 1.108 + throw new LZError("decompress", "becauseLZWrongMagicNumber", "Invalid header (no magic number"); 1.109 + } 1.110 + } 1.111 + 1.112 + let sizeBuf = 1.113 + ctypes.cast( 1.114 + SharedAll.offsetBy(ptr, MAGIC_NUMBER.byteLength), 1.115 + EXPECTED_SIZE_BUFFER_TYPE.ptr).contents; 1.116 + let expectDecompressedSize = 1.117 + sizeBuf[0] + (sizeBuf[1] << 8) + (sizeBuf[2] << 16) + (sizeBuf[3] << 24); 1.118 + if (expectDecompressedSize == 0) { 1.119 + // The underlying algorithm cannot handle a size of 0 1.120 + return new Uint8Array(0); 1.121 + } 1.122 + 1.123 + // Prepare the input buffer 1.124 + let inputPtr = SharedAll.offsetBy(ptr, HEADER_SIZE); 1.125 + 1.126 + // Prepare the output buffer 1.127 + let outputBuffer = new Uint8Array(expectDecompressedSize); 1.128 + let decompressedBytes = (new SharedAll.Type.size_t.implementation(0)); 1.129 + 1.130 + // Decompress 1.131 + let success = Internals.decompress(inputPtr, bytes - HEADER_SIZE, 1.132 + outputBuffer, outputBuffer.byteLength, 1.133 + decompressedBytes.address()); 1.134 + if (!success) { 1.135 + throw new LZError("decompress", "becauseLZInvalidContent", "Invalid content:Decompression stopped at " + decompressedBytes.value); 1.136 + } 1.137 + return new Uint8Array(outputBuffer.buffer, outputBuffer.byteOffset, decompressedBytes.value); 1.138 +} 1.139 +exports.decompressFileContent = decompressFileContent;