dca5809b by Brandon Casey Committed by GitHub

seperate aes-decrypter from hls (#748)

1 parent 356dda76
...@@ -11,7 +11,7 @@ Play back HLS with video.js, even where it's not natively supported. ...@@ -11,7 +11,7 @@ Play back HLS with video.js, even where it's not natively supported.
11 - [Installation](#installation) 11 - [Installation](#installation)
12 - [NPM](#npm) 12 - [NPM](#npm)
13 - [CDN](#cdn) 13 - [CDN](#cdn)
14 - [Local](#local) 14 - [Releases](#releases)
15 - [Manual Build](#manual-build) 15 - [Manual Build](#manual-build)
16 - [Contributing](#contributing) 16 - [Contributing](#contributing)
17 - [Getting Started](#getting-started) 17 - [Getting Started](#getting-started)
...@@ -42,7 +42,9 @@ Play back HLS with video.js, even where it's not natively supported. ...@@ -42,7 +42,9 @@ Play back HLS with video.js, even where it's not natively supported.
42 - [Testing](#testing) 42 - [Testing](#testing)
43 - [Release History](#release-history) 43 - [Release History](#release-history)
44 - [Building](#building) 44 - [Building](#building)
45 - [Development Commands](#development-commands) 45 - [Development](#development)
46 - [Tools](#tools)
47 - [Commands](#commands)
46 48
47 <!-- END doctoc generated TOC please keep comment here to allow auto update --> 49 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
48 50
......
...@@ -84,7 +84,7 @@ ...@@ -84,7 +84,7 @@
84 ], 84 ],
85 "dependencies": { 85 "dependencies": {
86 "m3u8-parser": "^1.0.2", 86 "m3u8-parser": "^1.0.2",
87 "pkcs7": "^0.2.2", 87 "aes-decrypter": "^1.0.3",
88 "video.js": "^5.10.1", 88 "video.js": "^5.10.1",
89 "videojs-contrib-media-sources": "^3.1.0", 89 "videojs-contrib-media-sources": "^3.1.0",
90 "videojs-swf": "^5.0.2" 90 "videojs-swf": "^5.0.2"
......
1 /**
2 * @file decrypter/aes.js
3 *
4 * This file contains an adaptation of the AES decryption algorithm
5 * from the Standford Javascript Cryptography Library. That work is
6 * covered by the following copyright and permissions notice:
7 *
8 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions are
13 * met:
14 *
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 *
18 * 2. Redistributions in binary form must reproduce the above
19 * copyright notice, this list of conditions and the following
20 * disclaimer in the documentation and/or other materials provided
21 * with the distribution.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
27 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
30 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
31 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
32 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
33 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 *
35 * The views and conclusions contained in the software and documentation
36 * are those of the authors and should not be interpreted as representing
37 * official policies, either expressed or implied, of the authors.
38 */
39
40 /**
41 * Expand the S-box tables.
42 *
43 * @private
44 */
45 const precompute = function() {
46 let tables = [[[], [], [], [], []], [[], [], [], [], []]];
47 let encTable = tables[0];
48 let decTable = tables[1];
49 let sbox = encTable[4];
50 let sboxInv = decTable[4];
51 let i;
52 let x;
53 let xInv;
54 let d = [];
55 let th = [];
56 let x2;
57 let x4;
58 let x8;
59 let s;
60 let tEnc;
61 let tDec;
62
63 // Compute double and third tables
64 for (i = 0; i < 256; i++) {
65 th[(d[i] = i << 1 ^ (i >> 7) * 283) ^ i] = i;
66 }
67
68 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
69 // Compute sbox
70 s = xInv ^ xInv << 1 ^ xInv << 2 ^ xInv << 3 ^ xInv << 4;
71 s = s >> 8 ^ s & 255 ^ 99;
72 sbox[x] = s;
73 sboxInv[s] = x;
74
75 // Compute MixColumns
76 x8 = d[x4 = d[x2 = d[x]]];
77 tDec = x8 * 0x1010101 ^ x4 * 0x10001 ^ x2 * 0x101 ^ x * 0x1010100;
78 tEnc = d[s] * 0x101 ^ s * 0x1010100;
79
80 for (i = 0; i < 4; i++) {
81 encTable[i][x] = tEnc = tEnc << 24 ^ tEnc >>> 8;
82 decTable[i][s] = tDec = tDec << 24 ^ tDec >>> 8;
83 }
84 }
85
86 // Compactify. Considerable speedup on Firefox.
87 for (i = 0; i < 5; i++) {
88 encTable[i] = encTable[i].slice(0);
89 decTable[i] = decTable[i].slice(0);
90 }
91 return tables;
92 };
93 let aesTables = null;
94
95 /**
96 * Schedule out an AES key for both encryption and decryption. This
97 * is a low-level class. Use a cipher mode to do bulk encryption.
98 *
99 * @class AES
100 * @param key {Array} The key as an array of 4, 6 or 8 words.
101 */
102 export default class AES {
103 constructor(key) {
104 /**
105 * The expanded S-box and inverse S-box tables. These will be computed
106 * on the client so that we don't have to send them down the wire.
107 *
108 * There are two tables, _tables[0] is for encryption and
109 * _tables[1] is for decryption.
110 *
111 * The first 4 sub-tables are the expanded S-box with MixColumns. The
112 * last (_tables[01][4]) is the S-box itself.
113 *
114 * @private
115 */
116 // if we have yet to precompute the S-box tables
117 // do so now
118 if (!aesTables) {
119 aesTables = precompute();
120 }
121 // then make a copy of that object for use
122 this._tables = [[aesTables[0][0].slice(),
123 aesTables[0][1].slice(),
124 aesTables[0][2].slice(),
125 aesTables[0][3].slice(),
126 aesTables[0][4].slice()],
127 [aesTables[1][0].slice(),
128 aesTables[1][1].slice(),
129 aesTables[1][2].slice(),
130 aesTables[1][3].slice(),
131 aesTables[1][4].slice()]];
132 let i;
133 let j;
134 let tmp;
135 let encKey;
136 let decKey;
137 let sbox = this._tables[0][4];
138 let decTable = this._tables[1];
139 let keyLen = key.length;
140 let rcon = 1;
141
142 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
143 throw new Error('Invalid aes key size');
144 }
145
146 encKey = key.slice(0);
147 decKey = [];
148 this._key = [encKey, decKey];
149
150 // schedule encryption keys
151 for (i = keyLen; i < 4 * keyLen + 28; i++) {
152 tmp = encKey[i - 1];
153
154 // apply sbox
155 if (i % keyLen === 0 || (keyLen === 8 && i % keyLen === 4)) {
156 tmp = sbox[tmp >>> 24] << 24 ^
157 sbox[tmp >> 16 & 255] << 16 ^
158 sbox[tmp >> 8 & 255] << 8 ^
159 sbox[tmp & 255];
160
161 // shift rows and add rcon
162 if (i % keyLen === 0) {
163 tmp = tmp << 8 ^ tmp >>> 24 ^ rcon << 24;
164 rcon = rcon << 1 ^ (rcon >> 7) * 283;
165 }
166 }
167
168 encKey[i] = encKey[i - keyLen] ^ tmp;
169 }
170
171 // schedule decryption keys
172 for (j = 0; i; j++, i--) {
173 tmp = encKey[j & 3 ? i : i - 4];
174 if (i <= 4 || j < 4) {
175 decKey[j] = tmp;
176 } else {
177 decKey[j] = decTable[0][sbox[tmp >>> 24 ]] ^
178 decTable[1][sbox[tmp >> 16 & 255]] ^
179 decTable[2][sbox[tmp >> 8 & 255]] ^
180 decTable[3][sbox[tmp & 255]];
181 }
182 }
183 }
184
185 /**
186 * Decrypt 16 bytes, specified as four 32-bit words.
187 *
188 * @param {Number} encrypted0 the first word to decrypt
189 * @param {Number} encrypted1 the second word to decrypt
190 * @param {Number} encrypted2 the third word to decrypt
191 * @param {Number} encrypted3 the fourth word to decrypt
192 * @param {Int32Array} out the array to write the decrypted words
193 * into
194 * @param {Number} offset the offset into the output array to start
195 * writing results
196 * @return {Array} The plaintext.
197 */
198 decrypt(encrypted0, encrypted1, encrypted2, encrypted3, out, offset) {
199 let key = this._key[1];
200 // state variables a,b,c,d are loaded with pre-whitened data
201 let a = encrypted0 ^ key[0];
202 let b = encrypted3 ^ key[1];
203 let c = encrypted2 ^ key[2];
204 let d = encrypted1 ^ key[3];
205 let a2;
206 let b2;
207 let c2;
208
209 // key.length === 2 ?
210 let nInnerRounds = key.length / 4 - 2;
211 let i;
212 let kIndex = 4;
213 let table = this._tables[1];
214
215 // load up the tables
216 let table0 = table[0];
217 let table1 = table[1];
218 let table2 = table[2];
219 let table3 = table[3];
220 let sbox = table[4];
221
222 // Inner rounds. Cribbed from OpenSSL.
223 for (i = 0; i < nInnerRounds; i++) {
224 a2 = table0[a >>> 24] ^
225 table1[b >> 16 & 255] ^
226 table2[c >> 8 & 255] ^
227 table3[d & 255] ^
228 key[kIndex];
229 b2 = table0[b >>> 24] ^
230 table1[c >> 16 & 255] ^
231 table2[d >> 8 & 255] ^
232 table3[a & 255] ^
233 key[kIndex + 1];
234 c2 = table0[c >>> 24] ^
235 table1[d >> 16 & 255] ^
236 table2[a >> 8 & 255] ^
237 table3[b & 255] ^
238 key[kIndex + 2];
239 d = table0[d >>> 24] ^
240 table1[a >> 16 & 255] ^
241 table2[b >> 8 & 255] ^
242 table3[c & 255] ^
243 key[kIndex + 3];
244 kIndex += 4;
245 a = a2; b = b2; c = c2;
246 }
247
248 // Last round.
249 for (i = 0; i < 4; i++) {
250 out[(3 & -i) + offset] =
251 sbox[a >>> 24] << 24 ^
252 sbox[b >> 16 & 255] << 16 ^
253 sbox[c >> 8 & 255] << 8 ^
254 sbox[d & 255] ^
255 key[kIndex++];
256 a2 = a; a = b; b = c; c = d; d = a2;
257 }
258 }
259 }
1 /**
2 * @file decrypter/async-stream.js
3 */
4 import Stream from '../stream';
5
6 /**
7 * A wrapper around the Stream class to use setTiemout
8 * and run stream "jobs" Asynchronously
9 *
10 * @class AsyncStream
11 * @extends Stream
12 */
13 export default class AsyncStream extends Stream {
14 constructor() {
15 super(Stream);
16 this.jobs = [];
17 this.delay = 1;
18 this.timeout_ = null;
19 }
20
21 /**
22 * process an async job
23 *
24 * @private
25 */
26 processJob_() {
27 this.jobs.shift()();
28 if (this.jobs.length) {
29 this.timeout_ = setTimeout(this.processJob_.bind(this),
30 this.delay);
31 } else {
32 this.timeout_ = null;
33 }
34 }
35
36 /**
37 * push a job into the stream
38 *
39 * @param {Function} job the job to push into the stream
40 */
41 push(job) {
42 this.jobs.push(job);
43 if (!this.timeout_) {
44 this.timeout_ = setTimeout(this.processJob_.bind(this),
45 this.delay);
46 }
47 }
48 }
49
1 /**
2 * @file decrypter/decrypter.js
3 *
4 * An asynchronous implementation of AES-128 CBC decryption with
5 * PKCS#7 padding.
6 */
7
8 import AES from './aes';
9 import AsyncStream from './async-stream';
10 import {unpad} from 'pkcs7';
11
12 /**
13 * Convert network-order (big-endian) bytes into their little-endian
14 * representation.
15 */
16 const ntoh = function(word) {
17 return (word << 24) |
18 ((word & 0xff00) << 8) |
19 ((word & 0xff0000) >> 8) |
20 (word >>> 24);
21 };
22
23 /**
24 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
25 *
26 * @param {Uint8Array} encrypted the encrypted bytes
27 * @param {Uint32Array} key the bytes of the decryption key
28 * @param {Uint32Array} initVector the initialization vector (IV) to
29 * use for the first round of CBC.
30 * @return {Uint8Array} the decrypted bytes
31 *
32 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
33 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
34 * @see https://tools.ietf.org/html/rfc2315
35 */
36 export const decrypt = function(encrypted, key, initVector) {
37 // word-level access to the encrypted bytes
38 let encrypted32 = new Int32Array(encrypted.buffer,
39 encrypted.byteOffset,
40 encrypted.byteLength >> 2);
41
42 let decipher = new AES(Array.prototype.slice.call(key));
43
44 // byte and word-level access for the decrypted output
45 let decrypted = new Uint8Array(encrypted.byteLength);
46 let decrypted32 = new Int32Array(decrypted.buffer);
47
48 // temporary variables for working with the IV, encrypted, and
49 // decrypted data
50 let init0;
51 let init1;
52 let init2;
53 let init3;
54 let encrypted0;
55 let encrypted1;
56 let encrypted2;
57 let encrypted3;
58
59 // iteration variable
60 let wordIx;
61
62 // pull out the words of the IV to ensure we don't modify the
63 // passed-in reference and easier access
64 init0 = initVector[0];
65 init1 = initVector[1];
66 init2 = initVector[2];
67 init3 = initVector[3];
68
69 // decrypt four word sequences, applying cipher-block chaining (CBC)
70 // to each decrypted block
71 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
72 // convert big-endian (network order) words into little-endian
73 // (javascript order)
74 encrypted0 = ntoh(encrypted32[wordIx]);
75 encrypted1 = ntoh(encrypted32[wordIx + 1]);
76 encrypted2 = ntoh(encrypted32[wordIx + 2]);
77 encrypted3 = ntoh(encrypted32[wordIx + 3]);
78
79 // decrypt the block
80 decipher.decrypt(encrypted0,
81 encrypted1,
82 encrypted2,
83 encrypted3,
84 decrypted32,
85 wordIx);
86
87 // XOR with the IV, and restore network byte-order to obtain the
88 // plaintext
89 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
90 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
91 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
92 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
93
94 // setup the IV for the next round
95 init0 = encrypted0;
96 init1 = encrypted1;
97 init2 = encrypted2;
98 init3 = encrypted3;
99 }
100
101 return decrypted;
102 };
103
104 /**
105 * The `Decrypter` class that manages decryption of AES
106 * data through `AsyncStream` objects and the `decrypt`
107 * function
108 *
109 * @param {Uint8Array} encrypted the encrypted bytes
110 * @param {Uint32Array} key the bytes of the decryption key
111 * @param {Uint32Array} initVector the initialization vector (IV) to
112 * @param {Function} done the function to run when done
113 * @class Decrypter
114 */
115 export class Decrypter {
116 constructor(encrypted, key, initVector, done) {
117 let step = Decrypter.STEP;
118 let encrypted32 = new Int32Array(encrypted.buffer);
119 let decrypted = new Uint8Array(encrypted.byteLength);
120 let i = 0;
121
122 this.asyncStream_ = new AsyncStream();
123
124 // split up the encryption job and do the individual chunks asynchronously
125 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
126 key,
127 initVector,
128 decrypted));
129 for (i = step; i < encrypted32.length; i += step) {
130 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
131 ntoh(encrypted32[i - 3]),
132 ntoh(encrypted32[i - 2]),
133 ntoh(encrypted32[i - 1])]);
134 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
135 key,
136 initVector,
137 decrypted));
138 }
139 // invoke the done() callback when everything is finished
140 this.asyncStream_.push(function() {
141 // remove pkcs#7 padding from the decrypted bytes
142 done(null, unpad(decrypted));
143 });
144 }
145
146 /**
147 * a getter for step the maximum number of bytes to process at one time
148 *
149 * @return {Number} the value of step 32000
150 */
151 static get STEP() {
152 // 4 * 8000;
153 return 32000;
154 }
155
156 /**
157 * @private
158 */
159 decryptChunk_(encrypted, key, initVector, decrypted) {
160 return function() {
161 let bytes = decrypt(encrypted, key, initVector);
162
163 decrypted.set(bytes, encrypted.byteOffset);
164 };
165 }
166 }
167
168 export default {
169 Decrypter,
170 decrypt
171 };
1 /**
2 * @file decrypter/index.js
3 *
4 * Index module to easily import the primary components of AES-128
5 * decryption. Like this:
6 *
7 * ```js
8 * import {Decrypter, decrypt, AsyncStream} from './src/decrypter';
9 * ```
10 */
11 import {decrypt, Decrypter} from './decrypter';
12 import AsyncStream from './async-stream';
13
14 export default {
15 decrypt,
16 Decrypter,
17 AsyncStream
18 };
...@@ -5,7 +5,7 @@ import Ranges from './ranges'; ...@@ -5,7 +5,7 @@ import Ranges from './ranges';
5 import {getMediaIndexForTime_ as getMediaIndexForTime, duration} from './playlist'; 5 import {getMediaIndexForTime_ as getMediaIndexForTime, duration} from './playlist';
6 import videojs from 'video.js'; 6 import videojs from 'video.js';
7 import SourceUpdater from './source-updater'; 7 import SourceUpdater from './source-updater';
8 import {Decrypter} from './decrypter'; 8 import {Decrypter} from 'aes-decrypter';
9 import Config from './config'; 9 import Config from './config';
10 10
11 // in ms 11 // in ms
......
...@@ -8,7 +8,7 @@ import document from 'global/document'; ...@@ -8,7 +8,7 @@ import document from 'global/document';
8 import PlaylistLoader from './playlist-loader'; 8 import PlaylistLoader from './playlist-loader';
9 import Playlist from './playlist'; 9 import Playlist from './playlist';
10 import xhrFactory from './xhr'; 10 import xhrFactory from './xhr';
11 import {Decrypter, AsyncStream, decrypt} from './decrypter'; 11 import {Decrypter, AsyncStream, decrypt} from 'aes-decrypter';
12 import utils from './bin-utils'; 12 import utils from './bin-utils';
13 import {MediaSource, URL} from 'videojs-contrib-media-sources'; 13 import {MediaSource, URL} from 'videojs-contrib-media-sources';
14 import m3u8 from 'm3u8-parser'; 14 import m3u8 from 'm3u8-parser';
......
1 // see docs/hlse.md for instructions on how test data was generated
2 import QUnit from 'qunit';
3 import {unpad} from 'pkcs7';
4 import sinon from 'sinon';
5 import {decrypt, Decrypter, AsyncStream} from '../src/decrypter';
6
7 // see docs/hlse.md for instructions on how test data was generated
8 const stringFromBytes = function(bytes) {
9 let result = '';
10
11 for (let i = 0; i < bytes.length; i++) {
12 result += String.fromCharCode(bytes[i]);
13 }
14 return result;
15 };
16
17 QUnit.module('Decryption');
18 QUnit.test('decrypts a single AES-128 with PKCS7 block', function() {
19 let key = new Uint32Array([0, 0, 0, 0]);
20 let initVector = key;
21 // the string "howdy folks" encrypted
22 let encrypted = new Uint8Array([
23 0xce, 0x90, 0x97, 0xd0,
24 0x08, 0x46, 0x4d, 0x18,
25 0x4f, 0xae, 0x01, 0x1c,
26 0x82, 0xa8, 0xf0, 0x67
27 ]);
28
29 QUnit.deepEqual('howdy folks',
30 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
31 'decrypted with a byte array key'
32 );
33 });
34
35 QUnit.test('decrypts multiple AES-128 blocks with CBC', function() {
36 let key = new Uint32Array([0, 0, 0, 0]);
37 let initVector = key;
38 // the string "0123456789abcdef01234" encrypted
39 let encrypted = new Uint8Array([
40 0x14, 0xf5, 0xfe, 0x74,
41 0x69, 0x66, 0xf2, 0x92,
42 0x65, 0x1c, 0x22, 0x88,
43 0xbb, 0xff, 0x46, 0x09,
44
45 0x0b, 0xde, 0x5e, 0x71,
46 0x77, 0x87, 0xeb, 0x84,
47 0xa9, 0x54, 0xc2, 0x45,
48 0xe9, 0x4e, 0x29, 0xb3
49 ]);
50
51 QUnit.deepEqual('0123456789abcdef01234',
52 stringFromBytes(unpad(decrypt(encrypted, key, initVector))),
53 'decrypted multiple blocks');
54 });
55
56 QUnit.test(
57 'verify that the deepcopy works by doing two decrypts in the same test',
58 function() {
59 let key = new Uint32Array([0, 0, 0, 0]);
60 let initVector = key;
61 // the string "howdy folks" encrypted
62 let pkcs7Block = new Uint8Array([
63 0xce, 0x90, 0x97, 0xd0,
64 0x08, 0x46, 0x4d, 0x18,
65 0x4f, 0xae, 0x01, 0x1c,
66 0x82, 0xa8, 0xf0, 0x67
67 ]);
68
69 QUnit.deepEqual('howdy folks',
70 stringFromBytes(unpad(decrypt(pkcs7Block, key, initVector))),
71 'decrypted with a byte array key'
72 );
73
74 // the string "0123456789abcdef01234" encrypted
75 let cbcBlocks = new Uint8Array([
76 0x14, 0xf5, 0xfe, 0x74,
77 0x69, 0x66, 0xf2, 0x92,
78 0x65, 0x1c, 0x22, 0x88,
79 0xbb, 0xff, 0x46, 0x09,
80
81 0x0b, 0xde, 0x5e, 0x71,
82 0x77, 0x87, 0xeb, 0x84,
83 0xa9, 0x54, 0xc2, 0x45,
84 0xe9, 0x4e, 0x29, 0xb3
85 ]);
86
87 QUnit.deepEqual('0123456789abcdef01234',
88 stringFromBytes(unpad(decrypt(cbcBlocks, key, initVector))),
89 'decrypted multiple blocks');
90
91 });
92
93 QUnit.module('Incremental Processing', {
94 beforeEach() {
95 this.clock = sinon.useFakeTimers();
96 },
97 afterEach() {
98 this.clock.restore();
99 }
100 });
101
102 QUnit.test('executes a callback after a timeout', function() {
103 let asyncStream = new AsyncStream();
104 let calls = '';
105
106 asyncStream.push(function() {
107 calls += 'a';
108 });
109
110 this.clock.tick(asyncStream.delay);
111 QUnit.equal(calls, 'a', 'invoked the callback once');
112 this.clock.tick(asyncStream.delay);
113 QUnit.equal(calls, 'a', 'only invoked the callback once');
114 });
115
116 QUnit.test('executes callback in series', function() {
117 let asyncStream = new AsyncStream();
118 let calls = '';
119
120 asyncStream.push(function() {
121 calls += 'a';
122 });
123 asyncStream.push(function() {
124 calls += 'b';
125 });
126
127 this.clock.tick(asyncStream.delay);
128 QUnit.equal(calls, 'a', 'invoked the first callback');
129 this.clock.tick(asyncStream.delay);
130 QUnit.equal(calls, 'ab', 'invoked the second');
131 });
132
133 QUnit.module('Incremental Decryption', {
134 beforeEach() {
135 this.clock = sinon.useFakeTimers();
136 },
137 afterEach() {
138 this.clock.restore();
139 }
140 });
141
142 QUnit.test('asynchronously decrypts a 4-word block', function() {
143 let key = new Uint32Array([0, 0, 0, 0]);
144 let initVector = key;
145 // the string "howdy folks" encrypted
146 let encrypted = new Uint8Array([0xce, 0x90, 0x97, 0xd0,
147 0x08, 0x46, 0x4d, 0x18,
148 0x4f, 0xae, 0x01, 0x1c,
149 0x82, 0xa8, 0xf0, 0x67]);
150 let decrypted;
151 let decrypter = new Decrypter(encrypted,
152 key,
153 initVector,
154 function(error, result) {
155 if (error) {
156 throw new Error(error);
157 }
158 decrypted = result;
159 });
160
161 QUnit.ok(!decrypted, 'asynchronously decrypts');
162 this.clock.tick(decrypter.asyncStream_.delay * 2);
163
164 QUnit.ok(decrypted, 'completed decryption');
165 QUnit.deepEqual('howdy folks',
166 stringFromBytes(decrypted),
167 'decrypts and unpads the result');
168 });
169
170 QUnit.test('breaks up input greater than the step value', function() {
171 let encrypted = new Int32Array(Decrypter.STEP + 4);
172 let done = false;
173 let decrypter = new Decrypter(encrypted,
174 new Uint32Array(4),
175 new Uint32Array(4),
176 function() {
177 done = true;
178 });
179
180 this.clock.tick(decrypter.asyncStream_.delay * 2);
181 QUnit.ok(!done, 'not finished after two ticks');
182
183 this.clock.tick(decrypter.asyncStream_.delay);
184 QUnit.ok(done, 'finished after the last chunk is decrypted');
185 });