├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── lz4.js ├── package.json ├── test └── cases │ ├── lz4.js │ └── xxh32.js ├── util.js └── xxh32.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "semistandard" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.0" 4 | - "4.1" 5 | - "0.12" 6 | - "0.10" 7 | before_script: 8 | - "npm install codecov.io istanbul" 9 | script: 10 | - "istanbul cover ./node_modules/mocha/bin/_mocha -- -R spec test/cases && cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lz4.js [![NPM version](https://badge.fury.io/js/lz4js.svg)](https://www.npmjs.com/package/lz4js) [![Build Status](https://travis-ci.org/Benzinga/lz4js.svg?branch=master)](https://travis-ci.org/Benzinga/lz4js) [![codecov](https://codecov.io/gh/Benzinga/lz4js/branch/master/graph/badge.svg)](https://codecov.io/gh/Benzinga/lz4js) 2 | Lz4.js is an implementation of Lz4 designed to be used in web browsers. It contains no dependencies on external libraries or Node.JS, though it is organized as a set of CommonJS modules. It is recommended to use Browserify or WebPack to bundle this for the web browser. 3 | 4 | ## Installation 5 | ``` 6 | npm install lz4js 7 | ``` 8 | 9 | ## Usage 10 | ```javascript 11 | var lz4 = require("lz4js"); 12 | var fs = require("fs"); 13 | 14 | // Compress 128 bytes of zero. 15 | var compressed = lz4.compress(new Array(128)); 16 | 17 | // Decompress. 18 | var decompressed = lz4.decompress(compressed); 19 | 20 | // Compress file.bin to file.lz4. 21 | var data = fs.readFileSync("file.bin"); 22 | compressed = Buffer.from(lz4.compress(data)); 23 | fs.writeFileSync('file.lz4', compressed); 24 | ``` 25 | 26 | > **Note**: The high-level `compress` and `decompress` functions deal with framed Lz4 data and do not support raw block data nor legacy Lz4 blocks. 27 | 28 | ## API 29 | The API accepts either `Array`s or `Uint8Array`s. Arrays are expected to be arrays of unsigned 8-bit values. The API will return `Uint8Array`s if the browser supports them, or `Array`s otherwise. 30 | 31 | - `compress(buffer: Array, maxSize: Number): Array` 32 | 33 | Compresses a buffer using Lz4. maxSize sets bounds on the output length; it is recommended to not specify this unless you know what you're doing. 34 | Any unused buffer data will be sliced before the buffer is returned. 35 | 36 | - `decompress(buffer: Array, maxSize: Number): Array` 37 | 38 | Decompresses a buffer using Lz4. maxSize sets bounds on the output length; if you know the output length, this will reduce memory usage somewhat. 39 | Any unused buffer data will be sliced before the buffer is returned. 40 | -------------------------------------------------------------------------------- /lz4.js: -------------------------------------------------------------------------------- 1 | // lz4.js - An implementation of Lz4 in plain JavaScript. 2 | // 3 | // TODO: 4 | // - Unify header parsing/writing. 5 | // - Support options (block size, checksums) 6 | // - Support streams 7 | // - Better error handling (handle bad offset, etc.) 8 | // - HC support (better search algorithm) 9 | // - Tests/benchmarking 10 | 11 | var xxhash = require('./xxh32.js'); 12 | var util = require('./util.js'); 13 | 14 | // Constants 15 | // -- 16 | 17 | // Compression format parameters/constants. 18 | var minMatch = 4; 19 | var minLength = 13; 20 | var searchLimit = 5; 21 | var skipTrigger = 6; 22 | var hashSize = 1 << 16; 23 | 24 | // Token constants. 25 | var mlBits = 4; 26 | var mlMask = (1 << mlBits) - 1; 27 | var runBits = 4; 28 | var runMask = (1 << runBits) - 1; 29 | 30 | // Shared buffers 31 | var blockBuf = makeBuffer(5 << 20); 32 | var hashTable = makeHashTable(); 33 | 34 | // Frame constants. 35 | var magicNum = 0x184D2204; 36 | 37 | // Frame descriptor flags. 38 | var fdContentChksum = 0x4; 39 | var fdContentSize = 0x8; 40 | var fdBlockChksum = 0x10; 41 | // var fdBlockIndep = 0x20; 42 | var fdVersion = 0x40; 43 | var fdVersionMask = 0xC0; 44 | 45 | // Block sizes. 46 | var bsUncompressed = 0x80000000; 47 | var bsDefault = 7; 48 | var bsShift = 4; 49 | var bsMask = 7; 50 | var bsMap = { 51 | 4: 0x10000, 52 | 5: 0x40000, 53 | 6: 0x100000, 54 | 7: 0x400000 55 | }; 56 | 57 | // Utility functions/primitives 58 | // -- 59 | 60 | // Makes our hashtable. On older browsers, may return a plain array. 61 | function makeHashTable () { 62 | try { 63 | return new Uint32Array(hashSize); 64 | } catch (error) { 65 | var hashTable = new Array(hashSize); 66 | 67 | for (var i = 0; i < hashSize; i++) { 68 | hashTable[i] = 0; 69 | } 70 | 71 | return hashTable; 72 | } 73 | } 74 | 75 | // Clear hashtable. 76 | function clearHashTable (table) { 77 | for (var i = 0; i < hashSize; i++) { 78 | hashTable[i] = 0; 79 | } 80 | } 81 | 82 | // Makes a byte buffer. On older browsers, may return a plain array. 83 | function makeBuffer (size) { 84 | try { 85 | return new Uint8Array(size); 86 | } catch (error) { 87 | var buf = new Array(size); 88 | 89 | for (var i = 0; i < size; i++) { 90 | buf[i] = 0; 91 | } 92 | 93 | return buf; 94 | } 95 | } 96 | 97 | function sliceArray (array, start, end) { 98 | if (typeof array.buffer !== undefined) { 99 | if (Uint8Array.prototype.slice) { 100 | return array.slice(start, end); 101 | } else { 102 | // Uint8Array#slice polyfill. 103 | var len = array.length; 104 | 105 | // Calculate start. 106 | start = start | 0; 107 | start = (start < 0) ? Math.max(len + start, 0) : Math.min(start, len); 108 | 109 | // Calculate end. 110 | end = (end === undefined) ? len : end | 0; 111 | end = (end < 0) ? Math.max(len + end, 0) : Math.min(end, len); 112 | 113 | // Copy into new array. 114 | var arraySlice = new Uint8Array(end - start); 115 | for (var i = start, n = 0; i < end;) { 116 | arraySlice[n++] = array[i++]; 117 | } 118 | 119 | return arraySlice; 120 | } 121 | } else { 122 | // Assume normal array. 123 | return array.slice(start, end); 124 | } 125 | } 126 | 127 | // Implementation 128 | // -- 129 | 130 | // Calculates an upper bound for lz4 compression. 131 | exports.compressBound = function compressBound (n) { 132 | return (n + (n / 255) + 16) | 0; 133 | }; 134 | 135 | // Calculates an upper bound for lz4 decompression, by reading the data. 136 | exports.decompressBound = function decompressBound (src) { 137 | var sIndex = 0; 138 | 139 | // Read magic number 140 | if (util.readU32(src, sIndex) !== magicNum) { 141 | throw new Error('invalid magic number'); 142 | } 143 | 144 | sIndex += 4; 145 | 146 | // Read descriptor 147 | var descriptor = src[sIndex++]; 148 | 149 | // Check version 150 | if ((descriptor & fdVersionMask) !== fdVersion) { 151 | throw new Error('incompatible descriptor version ' + (descriptor & fdVersionMask)); 152 | } 153 | 154 | // Read flags 155 | var useBlockSum = (descriptor & fdBlockChksum) !== 0; 156 | var useContentSize = (descriptor & fdContentSize) !== 0; 157 | 158 | // Read block size 159 | var bsIdx = (src[sIndex++] >> bsShift) & bsMask; 160 | 161 | if (bsMap[bsIdx] === undefined) { 162 | throw new Error('invalid block size ' + bsIdx); 163 | } 164 | 165 | var maxBlockSize = bsMap[bsIdx]; 166 | 167 | // Get content size 168 | if (useContentSize) { 169 | return util.readU64(src, sIndex); 170 | } 171 | 172 | // Checksum 173 | sIndex++; 174 | 175 | // Read blocks. 176 | var maxSize = 0; 177 | while (true) { 178 | var blockSize = util.readU32(src, sIndex); 179 | sIndex += 4; 180 | 181 | if (blockSize & bsUncompressed) { 182 | blockSize &= ~bsUncompressed; 183 | maxSize += blockSize; 184 | } else if (blockSize > 0) { 185 | maxSize += maxBlockSize; 186 | } 187 | 188 | if (blockSize === 0) { 189 | return maxSize; 190 | } 191 | 192 | if (useBlockSum) { 193 | sIndex += 4; 194 | } 195 | 196 | sIndex += blockSize; 197 | } 198 | }; 199 | 200 | // Creates a buffer of a given byte-size, falling back to plain arrays. 201 | exports.makeBuffer = makeBuffer; 202 | 203 | // Decompresses a block of Lz4. 204 | exports.decompressBlock = function decompressBlock (src, dst, sIndex, sLength, dIndex) { 205 | var mLength, mOffset, sEnd, n, i; 206 | var hasCopyWithin = dst.copyWithin !== undefined && dst.fill !== undefined; 207 | 208 | // Setup initial state. 209 | sEnd = sIndex + sLength; 210 | 211 | // Consume entire input block. 212 | while (sIndex < sEnd) { 213 | var token = src[sIndex++]; 214 | 215 | // Copy literals. 216 | var literalCount = (token >> 4); 217 | if (literalCount > 0) { 218 | // Parse length. 219 | if (literalCount === 0xf) { 220 | while (true) { 221 | literalCount += src[sIndex]; 222 | if (src[sIndex++] !== 0xff) { 223 | break; 224 | } 225 | } 226 | } 227 | 228 | // Copy literals 229 | for (n = sIndex + literalCount; sIndex < n;) { 230 | dst[dIndex++] = src[sIndex++]; 231 | } 232 | } 233 | 234 | if (sIndex >= sEnd) { 235 | break; 236 | } 237 | 238 | // Copy match. 239 | mLength = (token & 0xf); 240 | 241 | // Parse offset. 242 | mOffset = src[sIndex++] | (src[sIndex++] << 8); 243 | 244 | // Parse length. 245 | if (mLength === 0xf) { 246 | while (true) { 247 | mLength += src[sIndex]; 248 | if (src[sIndex++] !== 0xff) { 249 | break; 250 | } 251 | } 252 | } 253 | 254 | mLength += minMatch; 255 | 256 | // Copy match 257 | // prefer to use typedarray.copyWithin for larger matches 258 | // NOTE: copyWithin doesn't work as required by LZ4 for overlapping sequences 259 | // e.g. mOffset=1, mLength=30 (repeach char 30 times) 260 | // we special case the repeat char w/ array.fill 261 | if (hasCopyWithin && mOffset === 1) { 262 | dst.fill(dst[dIndex - 1] | 0, dIndex, dIndex + mLength); 263 | dIndex += mLength; 264 | } else if (hasCopyWithin && mOffset > mLength && mLength > 31) { 265 | dst.copyWithin(dIndex, dIndex - mOffset, dIndex - mOffset + mLength); 266 | dIndex += mLength; 267 | } else { 268 | for (i = dIndex - mOffset, n = i + mLength; i < n;) { 269 | dst[dIndex++] = dst[i++] | 0; 270 | } 271 | } 272 | } 273 | 274 | return dIndex; 275 | }; 276 | 277 | // Compresses a block with Lz4. 278 | exports.compressBlock = function compressBlock (src, dst, sIndex, sLength, hashTable) { 279 | var mIndex, mAnchor, mLength, mOffset, mStep; 280 | var literalCount, dIndex, sEnd, n; 281 | 282 | // Setup initial state. 283 | dIndex = 0; 284 | sEnd = sLength + sIndex; 285 | mAnchor = sIndex; 286 | 287 | // Process only if block is large enough. 288 | if (sLength >= minLength) { 289 | var searchMatchCount = (1 << skipTrigger) + 3; 290 | 291 | // Consume until last n literals (Lz4 spec limitation.) 292 | while (sIndex + minMatch < sEnd - searchLimit) { 293 | var seq = util.readU32(src, sIndex); 294 | var hash = util.hashU32(seq) >>> 0; 295 | 296 | // Crush hash to 16 bits. 297 | hash = ((hash >> 16) ^ hash) >>> 0 & 0xffff; 298 | 299 | // Look for a match in the hashtable. NOTE: remove one; see below. 300 | mIndex = hashTable[hash] - 1; 301 | 302 | // Put pos in hash table. NOTE: add one so that zero = invalid. 303 | hashTable[hash] = sIndex + 1; 304 | 305 | // Determine if there is a match (within range.) 306 | if (mIndex < 0 || ((sIndex - mIndex) >>> 16) > 0 || util.readU32(src, mIndex) !== seq) { 307 | mStep = searchMatchCount++ >> skipTrigger; 308 | sIndex += mStep; 309 | continue; 310 | } 311 | 312 | searchMatchCount = (1 << skipTrigger) + 3; 313 | 314 | // Calculate literal count and offset. 315 | literalCount = sIndex - mAnchor; 316 | mOffset = sIndex - mIndex; 317 | 318 | // We've already matched one word, so get that out of the way. 319 | sIndex += minMatch; 320 | mIndex += minMatch; 321 | 322 | // Determine match length. 323 | // N.B.: mLength does not include minMatch, Lz4 adds it back 324 | // in decoding. 325 | mLength = sIndex; 326 | while (sIndex < sEnd - searchLimit && src[sIndex] === src[mIndex]) { 327 | sIndex++; 328 | mIndex++; 329 | } 330 | mLength = sIndex - mLength; 331 | 332 | // Write token + literal count. 333 | var token = mLength < mlMask ? mLength : mlMask; 334 | if (literalCount >= runMask) { 335 | dst[dIndex++] = (runMask << mlBits) + token; 336 | for (n = literalCount - runMask; n >= 0xff; n -= 0xff) { 337 | dst[dIndex++] = 0xff; 338 | } 339 | dst[dIndex++] = n; 340 | } else { 341 | dst[dIndex++] = (literalCount << mlBits) + token; 342 | } 343 | 344 | // Write literals. 345 | for (var i = 0; i < literalCount; i++) { 346 | dst[dIndex++] = src[mAnchor + i]; 347 | } 348 | 349 | // Write offset. 350 | dst[dIndex++] = mOffset; 351 | dst[dIndex++] = (mOffset >> 8); 352 | 353 | // Write match length. 354 | if (mLength >= mlMask) { 355 | for (n = mLength - mlMask; n >= 0xff; n -= 0xff) { 356 | dst[dIndex++] = 0xff; 357 | } 358 | dst[dIndex++] = n; 359 | } 360 | 361 | // Move the anchor. 362 | mAnchor = sIndex; 363 | } 364 | } 365 | 366 | // Nothing was encoded. 367 | if (mAnchor === 0) { 368 | return 0; 369 | } 370 | 371 | // Write remaining literals. 372 | // Write literal token+count. 373 | literalCount = sEnd - mAnchor; 374 | if (literalCount >= runMask) { 375 | dst[dIndex++] = (runMask << mlBits); 376 | for (n = literalCount - runMask; n >= 0xff; n -= 0xff) { 377 | dst[dIndex++] = 0xff; 378 | } 379 | dst[dIndex++] = n; 380 | } else { 381 | dst[dIndex++] = (literalCount << mlBits); 382 | } 383 | 384 | // Write literals. 385 | sIndex = mAnchor; 386 | while (sIndex < sEnd) { 387 | dst[dIndex++] = src[sIndex++]; 388 | } 389 | 390 | return dIndex; 391 | }; 392 | 393 | // Decompresses a frame of Lz4 data. 394 | exports.decompressFrame = function decompressFrame (src, dst) { 395 | var useBlockSum, useContentSum, useContentSize, descriptor; 396 | var sIndex = 0; 397 | var dIndex = 0; 398 | 399 | // Read magic number 400 | if (util.readU32(src, sIndex) !== magicNum) { 401 | throw new Error('invalid magic number'); 402 | } 403 | 404 | sIndex += 4; 405 | 406 | // Read descriptor 407 | descriptor = src[sIndex++]; 408 | 409 | // Check version 410 | if ((descriptor & fdVersionMask) !== fdVersion) { 411 | throw new Error('incompatible descriptor version'); 412 | } 413 | 414 | // Read flags 415 | useBlockSum = (descriptor & fdBlockChksum) !== 0; 416 | useContentSum = (descriptor & fdContentChksum) !== 0; 417 | useContentSize = (descriptor & fdContentSize) !== 0; 418 | 419 | // Read block size 420 | var bsIdx = (src[sIndex++] >> bsShift) & bsMask; 421 | 422 | if (bsMap[bsIdx] === undefined) { 423 | throw new Error('invalid block size'); 424 | } 425 | 426 | if (useContentSize) { 427 | // TODO: read content size 428 | sIndex += 8; 429 | } 430 | 431 | sIndex++; 432 | 433 | // Read blocks. 434 | while (true) { 435 | var compSize; 436 | 437 | compSize = util.readU32(src, sIndex); 438 | sIndex += 4; 439 | 440 | if (compSize === 0) { 441 | break; 442 | } 443 | 444 | if (useBlockSum) { 445 | // TODO: read block checksum 446 | sIndex += 4; 447 | } 448 | 449 | // Check if block is compressed 450 | if ((compSize & bsUncompressed) !== 0) { 451 | // Mask off the 'uncompressed' bit 452 | compSize &= ~bsUncompressed; 453 | 454 | // Copy uncompressed data into destination buffer. 455 | for (var j = 0; j < compSize; j++) { 456 | dst[dIndex++] = src[sIndex++]; 457 | } 458 | } else { 459 | // Decompress into blockBuf 460 | dIndex = exports.decompressBlock(src, dst, sIndex, compSize, dIndex); 461 | sIndex += compSize; 462 | } 463 | } 464 | 465 | if (useContentSum) { 466 | // TODO: read content checksum 467 | sIndex += 4; 468 | } 469 | 470 | return dIndex; 471 | }; 472 | 473 | // Compresses data to an Lz4 frame. 474 | exports.compressFrame = function compressFrame (src, dst) { 475 | var dIndex = 0; 476 | 477 | // Write magic number. 478 | util.writeU32(dst, dIndex, magicNum); 479 | dIndex += 4; 480 | 481 | // Descriptor flags. 482 | dst[dIndex++] = fdVersion; 483 | dst[dIndex++] = bsDefault << bsShift; 484 | 485 | // Descriptor checksum. 486 | dst[dIndex] = xxhash.hash(0, dst, 4, dIndex - 4) >> 8; 487 | dIndex++; 488 | 489 | // Write blocks. 490 | var maxBlockSize = bsMap[bsDefault]; 491 | var remaining = src.length; 492 | var sIndex = 0; 493 | 494 | // Clear the hashtable. 495 | clearHashTable(hashTable); 496 | 497 | // Split input into blocks and write. 498 | while (remaining > 0) { 499 | var compSize = 0; 500 | var blockSize = remaining > maxBlockSize ? maxBlockSize : remaining; 501 | 502 | compSize = exports.compressBlock(src, blockBuf, sIndex, blockSize, hashTable); 503 | 504 | if (compSize > blockSize || compSize === 0) { 505 | // Output uncompressed. 506 | util.writeU32(dst, dIndex, 0x80000000 | blockSize); 507 | dIndex += 4; 508 | 509 | for (var z = sIndex + blockSize; sIndex < z;) { 510 | dst[dIndex++] = src[sIndex++]; 511 | } 512 | 513 | remaining -= blockSize; 514 | } else { 515 | // Output compressed. 516 | util.writeU32(dst, dIndex, compSize); 517 | dIndex += 4; 518 | 519 | for (var j = 0; j < compSize;) { 520 | dst[dIndex++] = blockBuf[j++]; 521 | } 522 | 523 | sIndex += blockSize; 524 | remaining -= blockSize; 525 | } 526 | } 527 | 528 | // Write blank end block. 529 | util.writeU32(dst, dIndex, 0); 530 | dIndex += 4; 531 | 532 | return dIndex; 533 | }; 534 | 535 | // Decompresses a buffer containing an Lz4 frame. maxSize is optional; if not 536 | // provided, a maximum size will be determined by examining the data. The 537 | // buffer returned will always be perfectly-sized. 538 | exports.decompress = function decompress (src, maxSize) { 539 | var dst, size; 540 | 541 | if (maxSize === undefined) { 542 | maxSize = exports.decompressBound(src); 543 | } 544 | dst = exports.makeBuffer(maxSize); 545 | size = exports.decompressFrame(src, dst); 546 | 547 | if (size !== maxSize) { 548 | dst = sliceArray(dst, 0, size); 549 | } 550 | 551 | return dst; 552 | }; 553 | 554 | // Compresses a buffer to an Lz4 frame. maxSize is optional; if not provided, 555 | // a buffer will be created based on the theoretical worst output size for a 556 | // given input size. The buffer returned will always be perfectly-sized. 557 | exports.compress = function compress (src, maxSize) { 558 | var dst, size; 559 | 560 | if (maxSize === undefined) { 561 | maxSize = exports.compressBound(src.length); 562 | } 563 | 564 | dst = exports.makeBuffer(maxSize); 565 | size = exports.compressFrame(src, dst); 566 | 567 | if (size !== maxSize) { 568 | dst = sliceArray(dst, 0, size); 569 | } 570 | 571 | return dst; 572 | }; 573 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lz4js", 3 | "version": "0.2.0", 4 | "description": "An Lz4 implementation for the browser.", 5 | "main": "lz4.js", 6 | "scripts": { 7 | "test": "mocha -R spec test/cases" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/Benzinga/lz4js.git" 12 | }, 13 | "keywords": [ 14 | "lz4", 15 | "compression", 16 | "decompression", 17 | "browser", 18 | "commonjs" 19 | ], 20 | "author": "John Chadwick", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/Benzinga/lz4js/issues" 24 | }, 25 | "homepage": "https://github.com/Benzinga/lz4js#readme", 26 | "devDependencies": { 27 | "chai": "^3.5.0", 28 | "eslint": "^3.0.1", 29 | "eslint-config-semistandard": "^6.0.2", 30 | "eslint-config-standard": "^5.3.5", 31 | "eslint-plugin-promise": "^2.0.0", 32 | "eslint-plugin-react": "^5.2.2", 33 | "eslint-plugin-standard": "^2.0.0", 34 | "mocha": "^2.5.3" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/cases/lz4.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | var expect = require('chai').expect; 3 | var lz4 = require('../../lz4'); 4 | 5 | // Find root object (depends on JS environment) 6 | var root; 7 | if (typeof window !== 'undefined') { 8 | root = window; 9 | } else if (typeof global !== 'undefined') { 10 | root = global; 11 | } 12 | 13 | // Use plain old arrays for older browsers and older Node. 14 | function byteArray (arg) { 15 | if (root.Uint8Array) { 16 | return new Uint8Array(arg); 17 | } else { 18 | if (typeof arg === 'number' || typeof arg === 'undefined') { 19 | return new Array(arg); 20 | } else { 21 | return arg; 22 | } 23 | } 24 | } 25 | 26 | describe('lz4', function () { 27 | describe('#decompress', function () { 28 | it('should decompress empty lz4 Array correctly', function () { 29 | var emptyLz4 = [4, 34, 77, 24, 64, 112, 223, 0, 0, 0, 0]; 30 | expect(lz4.decompress(emptyLz4)).to.be.deep.equal(byteArray(0)); 31 | }); 32 | 33 | it('should decompress empty lz4 Uint8Array correctly', function () { 34 | var emptyLz4 = byteArray([4, 34, 77, 24, 64, 112, 223, 0, 0, 0, 0]); 35 | expect(lz4.decompress(emptyLz4)).to.be.deep.equal(byteArray(0)); 36 | }); 37 | 38 | it('should decompress data compressed with lz4c', function () { 39 | var output = byteArray([ 40 | 0x54, 0x68, 0x65, 0x20, 0x77, 0x68, 0x6f, 0x6c, 41 | 0x65, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x20, 42 | 0x69, 0x73, 0x20, 0x65, 0x6e, 0x64, 0x69, 0x6e, 43 | 0x67, 0x2e, 0x0a 44 | ]); 45 | var input = byteArray([ 46 | 0x04, 0x22, 0x4d, 0x18, 0x64, 0x40, 0xa7, 0x1b, 47 | 0x00, 0x00, 0x80, 0x54, 0x68, 0x65, 0x20, 0x77, 48 | 0x68, 0x6f, 0x6c, 0x65, 0x20, 0x77, 0x6f, 0x72, 49 | 0x6c, 0x64, 0x20, 0x69, 0x73, 0x20, 0x65, 0x6e, 50 | 0x64, 0x69, 0x6e, 0x67, 0x2e, 0x0a, 0x00, 0x00, 51 | 0x00, 0x00, 0xbc, 0xa8, 0x6b, 0xc5 52 | ]); 53 | expect(lz4.decompress(input)).to.be.deep.equal(output); 54 | }); 55 | 56 | it('should decompress data compressed with lz4c (2)', function () { 57 | var output = byteArray([ 58 | 0x49, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x68, 0x61, 59 | 0x72, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x20, 0x79, 60 | 0x6f, 0x75, 0x0a, 0x49, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x69, 0x74, 61 | 0x20, 0x68, 0x61, 0x72, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x61, 0x6b, 62 | 0x65, 0x0a, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x70, 0x65, 0x6f, 0x70, 0x6c, 63 | 0x65, 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x69, 0x72, 64 | 0x63, 0x6c, 0x65, 0x73, 0x0a, 0x49, 0x74, 0x27, 0x73, 0x20, 0x61, 0x20, 65 | 0x76, 0x65, 0x72, 0x79, 0x2c, 0x20, 0x76, 0x65, 0x72, 0x79, 0x20, 0x6d, 66 | 0x61, 0x64, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x6d, 0x61, 67 | 0x64, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a 68 | ]); 69 | var input = byteArray([ 70 | 0x04, 0x22, 0x4d, 0x18, 0x64, 0x40, 0xa7, 0x67, 0x00, 0x00, 0x00, 0xff, 71 | 0x0c, 0x49, 0x20, 0x66, 0x69, 0x6e, 0x64, 0x20, 0x69, 0x74, 0x20, 0x68, 72 | 0x61, 0x72, 0x64, 0x20, 0x74, 0x6f, 0x20, 0x74, 0x65, 0x6c, 0x6c, 0x20, 73 | 0x79, 0x6f, 0x75, 0x0a, 0x1b, 0x00, 0x00, 0xf1, 0x1c, 0x61, 0x6b, 0x65, 74 | 0x0a, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x70, 0x65, 0x6f, 0x70, 0x6c, 0x65, 75 | 0x20, 0x72, 0x75, 0x6e, 0x20, 0x69, 0x6e, 0x20, 0x63, 0x69, 0x72, 0x63, 76 | 0x6c, 0x65, 0x73, 0x0a, 0x49, 0x74, 0x27, 0x73, 0x20, 0x61, 0x20, 0x76, 77 | 0x65, 0x72, 0x79, 0x2c, 0x06, 0x00, 0xf0, 0x07, 0x20, 0x6d, 0x61, 0x64, 78 | 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2c, 0x20, 0x6d, 0x61, 0x64, 0x20, 79 | 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x24, 0x2a, 80 | 0xaf, 0xb9 81 | ]); 82 | expect(lz4.decompress(input)).to.be.deep.equal(output); 83 | }); 84 | 85 | it('should decompress data containing content-size', function () { 86 | var output = byteArray([ 87 | 0x49, 0x66, 0x20, 0x79, 0x6f, 0x75, 0x20, 0x22, 88 | 0x77, 0x69, 0x6e, 0x2c, 0x22, 0x20, 0x79, 0x6f, 89 | 0x75, 0x20, 0x77, 0x6f, 0x6e, 0x27, 0x74, 0x20, 90 | 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 91 | 0x22, 0x70, 0x6c, 0x61, 0x79, 0x22, 0x20, 0x77, 92 | 0x69, 0x74, 0x68, 0x20, 0x6d, 0x65, 0x20, 0x61, 93 | 0x6e, 0x79, 0x6d, 0x6f, 0x72, 0x65, 0x2e, 0x0a 94 | ]); 95 | var input = byteArray([ 96 | 0x04, 0x22, 0x4d, 0x18, 0x6c, 0x40, 0x38, 0x00, 97 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0x38, 98 | 0x00, 0x00, 0x80, 0x49, 0x66, 0x20, 0x79, 0x6f, 99 | 0x75, 0x20, 0x22, 0x77, 0x69, 0x6e, 0x2c, 0x22, 100 | 0x20, 0x79, 0x6f, 0x75, 0x20, 0x77, 0x6f, 0x6e, 101 | 0x27, 0x74, 0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 102 | 0x74, 0x6f, 0x20, 0x22, 0x70, 0x6c, 0x61, 0x79, 103 | 0x22, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6d, 104 | 0x65, 0x20, 0x61, 0x6e, 0x79, 0x6d, 0x6f, 0x72, 105 | 0x65, 0x2e, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x9f, 106 | 0xda, 0xad, 0x19 107 | ]); 108 | expect(lz4.decompress(input)).to.be.deep.equal(output); 109 | }); 110 | 111 | it('should fail on bad header magic', function () { 112 | var input = byteArray([5, 34, 77, 24, 64, 112, 223, 0, 0, 0, 0]); 113 | var fn = function () { return lz4.decompress(input); }; 114 | expect(fn).to.throw(/invalid magic number/); 115 | }); 116 | 117 | it('should fail on bad block size', function () { 118 | var input = byteArray([4, 34, 77, 24, 64, 0, 223, 0, 0, 0, 0]); 119 | var fn = function () { return lz4.decompress(input); }; 120 | expect(fn).to.throw(/invalid block size/); 121 | }); 122 | }); 123 | 124 | describe('#decompressBlock', function () { 125 | it('should pass the Linux kernel lz4 test vector', function () { 126 | // This test comes from the Linux kernel, crypto/testmgr.h. 127 | // It is the string, "Join us now and share the software ", 128 | // repeated twice. It is probably quoting Richard Stallman's 129 | // "Free Software Song." Lz4 is used in the Linux kernel to 130 | // compress kernel images. 131 | var linuxTestIn = byteArray([ 132 | 0xf0, 0x10, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x75, 133 | 0x73, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x61, 0x6e, 134 | 0x64, 0x20, 0x73, 0x68, 0x61, 0x72, 0x65, 0x20, 135 | 0x74, 0x68, 0x65, 0x20, 0x73, 0x6f, 0x66, 0x74, 136 | 0x77, 0x0d, 0x00, 0x0f, 0x23, 0x00, 0x0b, 0x50, 137 | 0x77, 0x61, 0x72, 0x65, 0x20 138 | ]); 139 | var linuxTestOut = byteArray([ 140 | 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x75, 0x73, 0x20, 141 | 0x6e, 0x6f, 0x77, 0x20, 0x61, 0x6e, 0x64, 0x20, 142 | 0x73, 0x68, 0x61, 0x72, 0x65, 0x20, 0x74, 0x68, 143 | 0x65, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 144 | 0x72, 0x65, 0x20, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 145 | 0x75, 0x73, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x61, 146 | 0x6e, 0x64, 0x20, 0x73, 0x68, 0x61, 0x72, 0x65, 147 | 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x6f, 0x66, 148 | 0x74, 0x77, 0x61, 0x72, 0x65, 0x20 149 | ]); 150 | var testOut = byteArray(70); 151 | 152 | lz4.decompressBlock(linuxTestIn, testOut, 0, 45, 0); 153 | expect(testOut).to.be.deep.equal(linuxTestOut); 154 | }); 155 | }); 156 | 157 | describe('#compress', function () { 158 | it('should compress empty Array correctly', function () { 159 | var emptyLz4 = byteArray([4, 34, 77, 24, 64, 112, 223, 0, 0, 0, 0]); 160 | expect(lz4.compress([])).to.be.deep.equal(emptyLz4); 161 | }); 162 | 163 | it('should compress empty Uint8Array correctly', function () { 164 | var emptyLz4 = byteArray([4, 34, 77, 24, 64, 112, 223, 0, 0, 0, 0]); 165 | expect(lz4.compress(byteArray(0))).to.be.deep.equal(emptyLz4); 166 | }); 167 | 168 | it('should output pseudo RLE', function () { 169 | var input = byteArray([ 170 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 172 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 174 | ]); 175 | var output = byteArray([ 176 | 0x04, 0x22, 0x4D, 0x18, 0x40, 0x70, 0xDF, 0x0B, 177 | 0x00, 0x00, 0x00, 0x1F, 0x00, 0x01, 0x00, 0x07, 178 | 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 179 | 0x00, 0x00 180 | ]); 181 | expect(lz4.compress(input)).to.be.deep.equal(output); 182 | }); 183 | 184 | it('should find matches', function () { 185 | var input = byteArray([ 186 | 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 187 | 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 188 | 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74, 189 | 0x74, 0x65, 0x73, 0x74, 0x74, 0x65, 0x73, 0x74 190 | ]); 191 | 192 | // Psuedo-RLE describes when the length is greater than the offset, 193 | // i.e. the match goes beyond the cursor. This seems like non-sense, but 194 | // it works because this only occurs if there's a repeating pattern that 195 | // repeats _before_ the cursor - the match matches itself. 196 | var output = byteArray([ 197 | 0x04, 0x22, 0x4d, 0x18, 0x40, 0x70, 0xDF, 0x0E, 198 | 0x00, 0x00, 0x00, 0x4F, 0x74, 0x65, 0x73, 0x74, 199 | 0x04, 0x00, 0x04, 0x50, 0x74, 0x74, 0x65, 0x73, 200 | 0x74, 0x00, 0x00, 0x00, 0x00 201 | ]); 202 | 203 | expect(lz4.compress(input)).to.be.deep.equal(output); 204 | }); 205 | 206 | it('should use maxSize', function () { 207 | var emptyLz4 = byteArray([4, 34, 77, 24, 64, 112, 223, 0]); 208 | expect(lz4.compress([], 8)).to.be.deep.equal(emptyLz4); 209 | }); 210 | 211 | it('should not compress uncompressible data', function () { 212 | var input = byteArray([ 213 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 214 | 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 215 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 216 | 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F 217 | ]); 218 | 219 | // Make note of the 0x80000020 frame size - that last bit marks it 220 | // as being uncompressed. 221 | var output = byteArray([ 222 | 0x04, 0x22, 0x4d, 0x18, 0x40, 0x70, 0xdf, 0x20, 223 | 0x00, 0x00, 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 224 | 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 225 | 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 226 | 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 227 | 0x1d, 0x1e, 0x1f, 0x00, 0x00, 0x00, 0x00 228 | ]); 229 | 230 | expect(lz4.compress(input)).to.be.deep.equal(output); 231 | }); 232 | }); 233 | 234 | describe('#compressBlock', function () { 235 | it('should be able to output over 15 literals at end', function () { 236 | var input = byteArray([ 237 | 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x75, 0x73, 0x20, 238 | 0x6e, 0x6f, 0x77, 0x20, 0x61, 0x6e, 0x64, 0x20, 239 | 0x73, 0x68, 0x61, 0x72, 0x65, 0x20, 0x74, 0x68, 240 | 0x65, 0x20, 0x73, 0x6f, 0x66, 0x74, 0x77, 0x61, 241 | 0x72, 0x65, 0x20, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 242 | 0x75, 0x73, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x61, 243 | 0x6e, 0x64, 0x20, 0x73, 0x68, 0x61, 0x72, 0x65, 244 | 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x6f, 0x66, 245 | 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x01, 0x02, 246 | 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 247 | 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18 248 | ]); 249 | var output = byteArray([ 250 | 0xf0, 0x10, 0x4a, 0x6f, 0x69, 0x6e, 0x20, 0x75, 251 | 0x73, 0x20, 0x6e, 0x6f, 0x77, 0x20, 0x61, 0x6e, 252 | 0x64, 0x20, 0x73, 0x68, 0x61, 0x72, 0x65, 0x20, 253 | 0x74, 0x68, 0x65, 0x20, 0x73, 0x6f, 0x66, 0x74, 254 | 0x77, 0x0d, 0x00, 0x0f, 0x23, 0x00, 0x10, 0xf0, 255 | 0x03, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 256 | 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 257 | 0x16, 0x17, 0x18 258 | ]); 259 | var testOut = byteArray(59); 260 | var sz; 261 | 262 | sz = lz4.compressBlock(input, testOut, 0, input.length, lz4.makeBuffer(1 << 16)); 263 | 264 | expect(sz).to.be.equal(59); 265 | expect(testOut).to.be.deep.equal(output); 266 | }); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /test/cases/xxh32.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node, mocha */ 2 | var expect = require('chai').expect; 3 | var xxh32 = require('../../xxh32'); 4 | 5 | describe('xxh32', function () { 6 | describe('#hash', function () { 7 | it('passes the github.com/pierrec/xxHash tests', function () { 8 | var tests = [ 9 | [0x02cc5d05, []], 10 | [0x550d7456, [0x61]], 11 | [0x4999fc53, [0x61, 0x62]], 12 | [0x32d153ff, [0x61, 0x62, 0x63]], 13 | [0xa3643705, [0x61, 0x62, 0x63, 0x64]], 14 | [0x9738f19b, [0x61, 0x62, 0x63, 0x64, 0x65]], 15 | [0x8b7cd587, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66]], 16 | [0x9dd093b3, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67]], 17 | [0x0bb3c6bb, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68]], 18 | [0xd03c13fd, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 19 | 0x69]], 20 | [0x8b988cfe, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 21 | 0x69, 0x6a]], 22 | [0x9d2d8b62, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 23 | 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70]], 24 | [0x42ae804d, [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 25 | 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 26 | 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 27 | 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 28 | 0x36, 0x37, 0x38, 0x39]], 29 | [0x62b4ed00, [0x4c, 0x6f, 0x72, 0x65, 0x6d, 0x20, 0x69, 0x70, 30 | 0x73, 0x75, 0x6d, 0x20, 0x64, 0x6f, 0x6c, 0x6f, 31 | 0x72, 0x20, 0x73, 0x69, 0x74, 0x20, 0x61, 0x6d, 32 | 0x65, 0x74, 0x2c, 0x20, 0x63, 0x6f, 0x6e, 0x73, 33 | 0x65, 0x63, 0x74, 0x65, 0x74, 0x75, 0x72, 0x20, 34 | 0x61, 0x64, 0x69, 0x70, 0x69, 0x73, 0x63, 0x69, 35 | 0x6e, 0x67, 0x20, 0x65, 0x6c, 0x69, 0x74, 0x2c, 36 | 0x20, 0x73, 0x65, 0x64, 0x20, 0x64, 0x6f, 0x20, 37 | 0x65, 0x69, 0x75, 0x73, 0x6d, 0x6f, 0x64, 0x20, 38 | 0x74, 0x65, 0x6d, 0x70, 0x6f, 0x72, 0x20, 0x69, 39 | 0x6e, 0x63, 0x69, 0x64, 0x69, 0x64, 0x75, 0x6e, 40 | 0x74, 0x20, 0x75, 0x74, 0x20, 0x6c, 0x61, 0x62, 41 | 0x6f, 0x72, 0x65, 0x20, 0x65, 0x74, 0x20, 0x64, 42 | 0x6f, 0x6c, 0x6f, 0x72, 0x65, 0x20, 0x6d, 0x61, 43 | 0x67, 0x6e, 0x61, 0x20, 0x61, 0x6c, 0x69, 0x71, 44 | 0x75, 0x61, 0x2e, 0x20, 0x55, 0x74, 0x20, 0x65, 45 | 0x6e, 0x69, 0x6d, 0x20, 0x61, 0x64, 0x20, 0x6d, 46 | 0x69, 0x6e, 0x69, 0x6d, 0x20, 0x76, 0x65, 0x6e, 47 | 0x69, 0x61, 0x6d, 0x2c, 0x20, 0x71, 0x75, 0x69, 48 | 0x73, 0x20, 0x6e, 0x6f, 0x73, 0x74, 0x72, 0x75, 49 | 0x64, 0x20, 0x65, 0x78, 0x65, 0x72, 0x63, 0x69, 50 | 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x75, 51 | 0x6c, 0x6c, 0x61, 0x6d, 0x63, 0x6f, 0x20, 0x6c, 52 | 0x61, 0x62, 0x6f, 0x72, 0x69, 0x73, 0x20, 0x6e, 53 | 0x69, 0x73, 0x69, 0x20, 0x75, 0x74, 0x20, 0x61, 54 | 0x6c, 0x69, 0x71, 0x75, 0x69, 0x70, 0x20, 0x65, 55 | 0x78, 0x20, 0x65, 0x61, 0x20, 0x63, 0x6f, 0x6d, 56 | 0x6d, 0x6f, 0x64, 0x6f, 0x20, 0x63, 0x6f, 0x6e, 57 | 0x73, 0x65, 0x71, 0x75, 0x61, 0x74, 0x2e, 0x20, 58 | 0x44, 0x75, 0x69, 0x73, 0x20, 0x61, 0x75, 0x74, 59 | 0x65, 0x20, 0x69, 0x72, 0x75, 0x72, 0x65, 0x20, 60 | 0x64, 0x6f, 0x6c, 0x6f, 0x72, 0x20, 0x69, 0x6e, 61 | 0x20, 0x72, 0x65, 0x70, 0x72, 0x65, 0x68, 0x65, 62 | 0x6e, 0x64, 0x65, 0x72, 0x69, 0x74, 0x20, 0x69, 63 | 0x6e, 0x20, 0x76, 0x6f, 0x6c, 0x75, 0x70, 0x74, 64 | 0x61, 0x74, 0x65, 0x20, 0x76, 0x65, 0x6c, 0x69, 65 | 0x74, 0x20, 0x65, 0x73, 0x73, 0x65, 0x20, 0x63, 66 | 0x69, 0x6c, 0x6c, 0x75, 0x6d, 0x20, 0x64, 0x6f, 67 | 0x6c, 0x6f, 0x72, 0x65, 0x20, 0x65, 0x75, 0x20, 68 | 0x66, 0x75, 0x67, 0x69, 0x61, 0x74, 0x20, 0x6e, 69 | 0x75, 0x6c, 0x6c, 0x61, 0x20, 0x70, 0x61, 0x72, 70 | 0x69, 0x61, 0x74, 0x75, 0x72, 0x2e, 0x20, 0x45, 71 | 0x78, 0x63, 0x65, 0x70, 0x74, 0x65, 0x75, 0x72, 72 | 0x20, 0x73, 0x69, 0x6e, 0x74, 0x20, 0x6f, 0x63, 73 | 0x63, 0x61, 0x65, 0x63, 0x61, 0x74, 0x20, 0x63, 74 | 0x75, 0x70, 0x69, 0x64, 0x61, 0x74, 0x61, 0x74, 75 | 0x20, 0x6e, 0x6f, 0x6e, 0x20, 0x70, 0x72, 0x6f, 76 | 0x69, 0x64, 0x65, 0x6e, 0x74, 0x2c, 0x20, 0x73, 77 | 0x75, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x20, 0x63, 78 | 0x75, 0x6c, 0x70, 0x61, 0x20, 0x71, 0x75, 0x69, 79 | 0x20, 0x6f, 0x66, 0x66, 0x69, 0x63, 0x69, 0x61, 80 | 0x20, 0x64, 0x65, 0x73, 0x65, 0x72, 0x75, 0x6e, 81 | 0x74, 0x20, 0x6d, 0x6f, 0x6c, 0x6c, 0x69, 0x74, 82 | 0x20, 0x61, 0x6e, 0x69, 0x6d, 0x20, 0x69, 0x64, 83 | 0x20, 0x65, 0x73, 0x74, 0x20, 0x6c, 0x61, 0x62, 84 | 0x6f, 0x72, 0x75, 0x6d, 0x2e]] 85 | ]; 86 | 87 | for (var i = 0; i < tests.length; ++i) { 88 | var expected = tests[i][0]; 89 | var vector = tests[i][1]; 90 | expect(xxh32.hash(0, vector, 0, vector.length)).to.be.equal(expected); 91 | } 92 | }); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | // Simple hash function, from: http://burtleburtle.net/bob/hash/integer.html. 2 | // Chosen because it doesn't use multiply and achieves full avalanche. 3 | exports.hashU32 = function hashU32(a) { 4 | a = a | 0; 5 | a = a + 2127912214 + (a << 12) | 0; 6 | a = a ^ -949894596 ^ a >>> 19; 7 | a = a + 374761393 + (a << 5) | 0; 8 | a = a + -744332180 ^ a << 9; 9 | a = a + -42973499 + (a << 3) | 0; 10 | return a ^ -1252372727 ^ a >>> 16 | 0; 11 | } 12 | 13 | // Reads a 64-bit little-endian integer from an array. 14 | exports.readU64 = function readU64(b, n) { 15 | var x = 0; 16 | x |= b[n++] << 0; 17 | x |= b[n++] << 8; 18 | x |= b[n++] << 16; 19 | x |= b[n++] << 24; 20 | x |= b[n++] << 32; 21 | x |= b[n++] << 40; 22 | x |= b[n++] << 48; 23 | x |= b[n++] << 56; 24 | return x; 25 | } 26 | 27 | // Reads a 32-bit little-endian integer from an array. 28 | exports.readU32 = function readU32(b, n) { 29 | var x = 0; 30 | x |= b[n++] << 0; 31 | x |= b[n++] << 8; 32 | x |= b[n++] << 16; 33 | x |= b[n++] << 24; 34 | return x; 35 | } 36 | 37 | // Writes a 32-bit little-endian integer from an array. 38 | exports.writeU32 = function writeU32(b, n, x) { 39 | b[n++] = (x >> 0) & 0xff; 40 | b[n++] = (x >> 8) & 0xff; 41 | b[n++] = (x >> 16) & 0xff; 42 | b[n++] = (x >> 24) & 0xff; 43 | } 44 | 45 | // Multiplies two numbers using 32-bit integer multiplication. 46 | // Algorithm from Emscripten. 47 | exports.imul = function imul(a, b) { 48 | var ah = a >>> 16; 49 | var al = a & 65535; 50 | var bh = b >>> 16; 51 | var bl = b & 65535; 52 | 53 | return al * bl + (ah * bl + al * bh << 16) | 0; 54 | }; -------------------------------------------------------------------------------- /xxh32.js: -------------------------------------------------------------------------------- 1 | // xxh32.js - implementation of xxhash32 in plain JavaScript 2 | var util = require('./util.js'); 3 | 4 | // xxhash32 primes 5 | var prime1 = 0x9e3779b1; 6 | var prime2 = 0x85ebca77; 7 | var prime3 = 0xc2b2ae3d; 8 | var prime4 = 0x27d4eb2f; 9 | var prime5 = 0x165667b1; 10 | 11 | // Utility functions/primitives 12 | // -- 13 | 14 | function rotl32 (x, r) { 15 | x = x | 0; 16 | r = r | 0; 17 | 18 | return x >>> (32 - r | 0) | x << r | 0; 19 | } 20 | 21 | function rotmul32 (h, r, m) { 22 | h = h | 0; 23 | r = r | 0; 24 | m = m | 0; 25 | 26 | return util.imul(h >>> (32 - r | 0) | h << r, m) | 0; 27 | } 28 | 29 | function shiftxor32 (h, s) { 30 | h = h | 0; 31 | s = s | 0; 32 | 33 | return h >>> s ^ h | 0; 34 | } 35 | 36 | // Implementation 37 | // -- 38 | 39 | function xxhapply (h, src, m0, s, m1) { 40 | return rotmul32(util.imul(src, m0) + h, s, m1); 41 | } 42 | 43 | function xxh1 (h, src, index) { 44 | return rotmul32((h + util.imul(src[index], prime5)), 11, prime1); 45 | } 46 | 47 | function xxh4 (h, src, index) { 48 | return xxhapply(h, util.readU32(src, index), prime3, 17, prime4); 49 | } 50 | 51 | function xxh16 (h, src, index) { 52 | return [ 53 | xxhapply(h[0], util.readU32(src, index + 0), prime2, 13, prime1), 54 | xxhapply(h[1], util.readU32(src, index + 4), prime2, 13, prime1), 55 | xxhapply(h[2], util.readU32(src, index + 8), prime2, 13, prime1), 56 | xxhapply(h[3], util.readU32(src, index + 12), prime2, 13, prime1) 57 | ]; 58 | } 59 | 60 | function xxh32 (seed, src, index, len) { 61 | var h, l; 62 | l = len; 63 | if (len >= 16) { 64 | h = [ 65 | seed + prime1 + prime2, 66 | seed + prime2, 67 | seed, 68 | seed - prime1 69 | ]; 70 | 71 | while (len >= 16) { 72 | h = xxh16(h, src, index); 73 | 74 | index += 16; 75 | len -= 16; 76 | } 77 | 78 | h = rotl32(h[0], 1) + rotl32(h[1], 7) + rotl32(h[2], 12) + rotl32(h[3], 18) + l; 79 | } else { 80 | h = (seed + prime5 + len) >>> 0; 81 | } 82 | 83 | while (len >= 4) { 84 | h = xxh4(h, src, index); 85 | 86 | index += 4; 87 | len -= 4; 88 | } 89 | 90 | while (len > 0) { 91 | h = xxh1(h, src, index); 92 | 93 | index++; 94 | len--; 95 | } 96 | 97 | h = shiftxor32(util.imul(shiftxor32(util.imul(shiftxor32(h, 15), prime2), 13), prime3), 16); 98 | 99 | return h >>> 0; 100 | } 101 | 102 | exports.hash = xxh32; 103 | --------------------------------------------------------------------------------