Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | if (typeof Components != "undefined") { |
michael@0 | 8 | throw new Error("This file is meant to be loaded in a worker"); |
michael@0 | 9 | } |
michael@0 | 10 | if (!module || !exports) { |
michael@0 | 11 | throw new Error("Please load this module with require()"); |
michael@0 | 12 | } |
michael@0 | 13 | |
michael@0 | 14 | const SharedAll = require("resource://gre/modules/osfile/osfile_shared_allthreads.jsm"); |
michael@0 | 15 | const Internals = require("resource://gre/modules/workers/lz4_internal.js"); |
michael@0 | 16 | |
michael@0 | 17 | const MAGIC_NUMBER = new Uint8Array([109, 111, 122, 76, 122, 52, 48, 0]); // "mozLz4a\0" |
michael@0 | 18 | |
michael@0 | 19 | const BYTES_IN_SIZE_HEADER = ctypes.uint32_t.size; |
michael@0 | 20 | |
michael@0 | 21 | const HEADER_SIZE = MAGIC_NUMBER.byteLength + BYTES_IN_SIZE_HEADER; |
michael@0 | 22 | |
michael@0 | 23 | const EXPECTED_HEADER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, HEADER_SIZE); |
michael@0 | 24 | const EXPECTED_SIZE_BUFFER_TYPE = new ctypes.ArrayType(ctypes.uint8_t, BYTES_IN_SIZE_HEADER); |
michael@0 | 25 | |
michael@0 | 26 | /** |
michael@0 | 27 | * An error during (de)compression |
michael@0 | 28 | * |
michael@0 | 29 | * @param {string} operation The name of the operation ("compress", "decompress") |
michael@0 | 30 | * @param {string} reason A reason to be used when matching errors. Must start |
michael@0 | 31 | * with "because", e.g. "becauseInvalidContent". |
michael@0 | 32 | * @param {string} message A human-readable message. |
michael@0 | 33 | */ |
michael@0 | 34 | function LZError(operation, reason, message) { |
michael@0 | 35 | SharedAll.OSError.call(this); |
michael@0 | 36 | this.operation = operation; |
michael@0 | 37 | this[reason] = true; |
michael@0 | 38 | this.message = message; |
michael@0 | 39 | } |
michael@0 | 40 | LZError.prototype = Object.create(SharedAll.OSError); |
michael@0 | 41 | LZError.prototype.toString = function toString() { |
michael@0 | 42 | return this.message; |
michael@0 | 43 | }; |
michael@0 | 44 | exports.Error = LZError; |
michael@0 | 45 | |
michael@0 | 46 | /** |
michael@0 | 47 | * Compress a block to a form suitable for writing to disk. |
michael@0 | 48 | * |
michael@0 | 49 | * Compatibility note: For the moment, we are basing our code on lz4 |
michael@0 | 50 | * 1.3, which does not specify a *file* format. Therefore, we define |
michael@0 | 51 | * our own format. Once lz4 defines a complete file format, we will |
michael@0 | 52 | * migrate both |compressFileContent| and |decompressFileContent| to this file |
michael@0 | 53 | * format. For backwards-compatibility, |decompressFileContent| will however |
michael@0 | 54 | * keep the ability to decompress files provided with older versions of |
michael@0 | 55 | * |compressFileContent|. |
michael@0 | 56 | * |
michael@0 | 57 | * Compressed files have the following layout: |
michael@0 | 58 | * |
michael@0 | 59 | * | MAGIC_NUMBER (8 bytes) | content size (uint32_t, little endian) | content, as obtained from lz4_compress | |
michael@0 | 60 | * |
michael@0 | 61 | * @param {TypedArray|void*} buffer The buffer to write to the disk. |
michael@0 | 62 | * @param {object=} options An object that may contain the following fields: |
michael@0 | 63 | * - {number} bytes The number of bytes to read from |buffer|. If |buffer| |
michael@0 | 64 | * is an |ArrayBuffer|, |bytes| defaults to |buffer.byteLength|. If |
michael@0 | 65 | * |buffer| is a |void*|, |bytes| MUST be provided. |
michael@0 | 66 | * @return {Uint8Array} An array of bytes suitable for being written to the |
michael@0 | 67 | * disk. |
michael@0 | 68 | */ |
michael@0 | 69 | function compressFileContent(array, options = {}) { |
michael@0 | 70 | // Prepare the output array |
michael@0 | 71 | let inputBytes; |
michael@0 | 72 | if (SharedAll.isTypedArray(array) && !(options && "bytes" in options)) { |
michael@0 | 73 | inputBytes = array.byteLength; |
michael@0 | 74 | } else if (options && options.bytes) { |
michael@0 | 75 | inputBytes = options.bytes; |
michael@0 | 76 | } else { |
michael@0 | 77 | throw new TypeError("compressFileContent requires a size"); |
michael@0 | 78 | } |
michael@0 | 79 | let maxCompressedSize = Internals.maxCompressedSize(inputBytes); |
michael@0 | 80 | let outputArray = new Uint8Array(HEADER_SIZE + maxCompressedSize); |
michael@0 | 81 | |
michael@0 | 82 | // Compress to output array |
michael@0 | 83 | let payload = new Uint8Array(outputArray.buffer, outputArray.byteOffset + HEADER_SIZE); |
michael@0 | 84 | let compressedSize = Internals.compress(array, inputBytes, payload); |
michael@0 | 85 | |
michael@0 | 86 | // Add headers |
michael@0 | 87 | outputArray.set(MAGIC_NUMBER); |
michael@0 | 88 | let view = new DataView(outputArray.buffer); |
michael@0 | 89 | view.setUint32(MAGIC_NUMBER.byteLength, inputBytes, true); |
michael@0 | 90 | |
michael@0 | 91 | return new Uint8Array(outputArray.buffer, 0, HEADER_SIZE + compressedSize); |
michael@0 | 92 | } |
michael@0 | 93 | exports.compressFileContent = compressFileContent; |
michael@0 | 94 | |
michael@0 | 95 | function decompressFileContent(array, options = {}) { |
michael@0 | 96 | let {ptr, bytes} = SharedAll.normalizeToPointer(array, options.bytes || null); |
michael@0 | 97 | if (bytes < HEADER_SIZE) { |
michael@0 | 98 | throw new LZError("decompress", "becauseLZNoHeader", "Buffer is too short (no header)"); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | // Read headers |
michael@0 | 102 | let expectMagicNumber = ctypes.cast(ptr, EXPECTED_HEADER_TYPE.ptr).contents; |
michael@0 | 103 | for (let i = 0; i < MAGIC_NUMBER.byteLength; ++i) { |
michael@0 | 104 | if (expectMagicNumber[i] != MAGIC_NUMBER[i]) { |
michael@0 | 105 | throw new LZError("decompress", "becauseLZWrongMagicNumber", "Invalid header (no magic number"); |
michael@0 | 106 | } |
michael@0 | 107 | } |
michael@0 | 108 | |
michael@0 | 109 | let sizeBuf = |
michael@0 | 110 | ctypes.cast( |
michael@0 | 111 | SharedAll.offsetBy(ptr, MAGIC_NUMBER.byteLength), |
michael@0 | 112 | EXPECTED_SIZE_BUFFER_TYPE.ptr).contents; |
michael@0 | 113 | let expectDecompressedSize = |
michael@0 | 114 | sizeBuf[0] + (sizeBuf[1] << 8) + (sizeBuf[2] << 16) + (sizeBuf[3] << 24); |
michael@0 | 115 | if (expectDecompressedSize == 0) { |
michael@0 | 116 | // The underlying algorithm cannot handle a size of 0 |
michael@0 | 117 | return new Uint8Array(0); |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | // Prepare the input buffer |
michael@0 | 121 | let inputPtr = SharedAll.offsetBy(ptr, HEADER_SIZE); |
michael@0 | 122 | |
michael@0 | 123 | // Prepare the output buffer |
michael@0 | 124 | let outputBuffer = new Uint8Array(expectDecompressedSize); |
michael@0 | 125 | let decompressedBytes = (new SharedAll.Type.size_t.implementation(0)); |
michael@0 | 126 | |
michael@0 | 127 | // Decompress |
michael@0 | 128 | let success = Internals.decompress(inputPtr, bytes - HEADER_SIZE, |
michael@0 | 129 | outputBuffer, outputBuffer.byteLength, |
michael@0 | 130 | decompressedBytes.address()); |
michael@0 | 131 | if (!success) { |
michael@0 | 132 | throw new LZError("decompress", "becauseLZInvalidContent", "Invalid content:Decompression stopped at " + decompressedBytes.value); |
michael@0 | 133 | } |
michael@0 | 134 | return new Uint8Array(outputBuffer.buffer, outputBuffer.byteOffset, decompressedBytes.value); |
michael@0 | 135 | } |
michael@0 | 136 | exports.decompressFileContent = decompressFileContent; |