toolkit/components/workerlz4/lz4.js

Fri, 16 Jan 2015 18:13:44 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Fri, 16 Jan 2015 18:13:44 +0100
branch
TOR_BUG_9701
changeset 14
925c144e1f1f
permissions
-rw-r--r--

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;

mercurial