• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24if (!common.hasCrypto)
25  common.skip('missing crypto');
26
27const assert = require('assert');
28const crypto = require('crypto');
29const stream = require('stream');
30const zlib = require('zlib');
31
32const Stream = stream.Stream;
33
34// Emit random bytes, and keep a shasum
35class RandomReadStream extends Stream {
36  constructor(opt) {
37    super();
38
39    this.readable = true;
40    this._paused = false;
41    this._processing = false;
42
43    this._hasher = crypto.createHash('sha1');
44    opt = opt || {};
45
46    // base block size.
47    opt.block = opt.block || 256 * 1024;
48
49    // Total number of bytes to emit
50    opt.total = opt.total || 256 * 1024 * 1024;
51    this._remaining = opt.total;
52
53    // How variable to make the block sizes
54    opt.jitter = opt.jitter || 1024;
55
56    this._opt = opt;
57
58    this._process = this._process.bind(this);
59
60    process.nextTick(this._process);
61  }
62
63  pause() {
64    this._paused = true;
65    this.emit('pause');
66  }
67
68  resume() {
69    // console.error("rrs resume");
70    this._paused = false;
71    this.emit('resume');
72    this._process();
73  }
74
75  _process() {
76    if (this._processing) return;
77    if (this._paused) return;
78
79    this._processing = true;
80
81    if (!this._remaining) {
82      this._hash = this._hasher.digest('hex').toLowerCase().trim();
83      this._processing = false;
84
85      this.emit('end');
86      return;
87    }
88
89    // Figure out how many bytes to output
90    // if finished, then just emit end.
91    let block = this._opt.block;
92    const jitter = this._opt.jitter;
93    if (jitter) {
94      block += Math.ceil(Math.random() * jitter - (jitter / 2));
95    }
96    block = Math.min(block, this._remaining);
97    const buf = Buffer.allocUnsafe(block);
98    for (let i = 0; i < block; i++) {
99      buf[i] = Math.random() * 256;
100    }
101
102    this._hasher.update(buf);
103
104    this._remaining -= block;
105
106    this._processing = false;
107
108    this.emit('data', buf);
109    process.nextTick(this._process);
110  }
111}
112
113// A filter that just verifies a shasum
114class HashStream extends Stream {
115  constructor() {
116    super();
117    this.readable = this.writable = true;
118    this._hasher = crypto.createHash('sha1');
119  }
120
121  write(c) {
122    // Simulate the way that an fs.ReadStream returns false
123    // on *every* write, only to resume a moment later.
124    this._hasher.update(c);
125    process.nextTick(() => this.resume());
126    return false;
127  }
128
129  resume() {
130    this.emit('resume');
131    process.nextTick(() => this.emit('drain'));
132  }
133
134  end(c) {
135    if (c) {
136      this.write(c);
137    }
138    this._hash = this._hasher.digest('hex').toLowerCase().trim();
139    this.emit('data', this._hash);
140    this.emit('end');
141  }
142}
143
144for (const [ createCompress, createDecompress ] of [
145  [ zlib.createGzip, zlib.createGunzip ],
146  [ zlib.createBrotliCompress, zlib.createBrotliDecompress ],
147]) {
148  const inp = new RandomReadStream({ total: 1024, block: 256, jitter: 16 });
149  const out = new HashStream();
150  const gzip = createCompress();
151  const gunz = createDecompress();
152
153  inp.pipe(gzip).pipe(gunz).pipe(out);
154
155  out.on('data', common.mustCall((c) => {
156    assert.strictEqual(c, inp._hash, `Hash '${c}' equals '${inp._hash}'.`);
157  }));
158}
159