tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

sender.test.js (11363B)


      1 'use strict';
      2 
      3 const assert = require('assert');
      4 
      5 const extension = require('../lib/extension');
      6 const PerMessageDeflate = require('../lib/permessage-deflate');
      7 const Sender = require('../lib/sender');
      8 const { EMPTY_BUFFER } = require('../lib/constants');
      9 
     10 class MockSocket {
     11  constructor({ write } = {}) {
     12    this.readable = true;
     13    this.writable = true;
     14 
     15    if (write) this.write = write;
     16  }
     17 
     18  cork() {}
     19  write() {}
     20  uncork() {}
     21 }
     22 
     23 describe('Sender', () => {
     24  describe('.frame', () => {
     25    it('does not mutate the input buffer if data is `readOnly`', () => {
     26      const buf = Buffer.from([1, 2, 3, 4, 5]);
     27 
     28      Sender.frame(buf, {
     29        readOnly: true,
     30        rsv1: false,
     31        mask: true,
     32        opcode: 2,
     33        fin: true
     34      });
     35 
     36      assert.ok(buf.equals(Buffer.from([1, 2, 3, 4, 5])));
     37    });
     38 
     39    it('honors the `rsv1` option', () => {
     40      const list = Sender.frame(EMPTY_BUFFER, {
     41        readOnly: false,
     42        mask: false,
     43        rsv1: true,
     44        opcode: 1,
     45        fin: true
     46      });
     47 
     48      assert.strictEqual(list[0][0] & 0x40, 0x40);
     49    });
     50 
     51    it('accepts a string as first argument', () => {
     52      const list = Sender.frame('€', {
     53        readOnly: false,
     54        rsv1: false,
     55        mask: false,
     56        opcode: 1,
     57        fin: true
     58      });
     59 
     60      assert.deepStrictEqual(list[0], Buffer.from('8103', 'hex'));
     61      assert.deepStrictEqual(list[1], Buffer.from('e282ac', 'hex'));
     62    });
     63  });
     64 
     65  describe('#send', () => {
     66    it('compresses data if compress option is enabled', (done) => {
     67      const chunks = [];
     68      const perMessageDeflate = new PerMessageDeflate();
     69      const mockSocket = new MockSocket({
     70        write: (chunk) => {
     71          chunks.push(chunk);
     72          if (chunks.length !== 6) return;
     73 
     74          assert.strictEqual(chunks[0].length, 2);
     75          assert.strictEqual(chunks[0][0] & 0x40, 0x40);
     76 
     77          assert.strictEqual(chunks[2].length, 2);
     78          assert.strictEqual(chunks[2][0] & 0x40, 0x40);
     79 
     80          assert.strictEqual(chunks[4].length, 2);
     81          assert.strictEqual(chunks[4][0] & 0x40, 0x40);
     82          done();
     83        }
     84      });
     85      const sender = new Sender(mockSocket, {
     86        'permessage-deflate': perMessageDeflate
     87      });
     88 
     89      perMessageDeflate.accept([{}]);
     90 
     91      const options = { compress: true, fin: true };
     92      const array = new Uint8Array([0x68, 0x69]);
     93 
     94      sender.send(array.buffer, options);
     95      sender.send(array, options);
     96      sender.send('hi', options);
     97    });
     98 
     99    describe('when context takeover is disabled', () => {
    100      it('honors the compression threshold', (done) => {
    101        const chunks = [];
    102        const perMessageDeflate = new PerMessageDeflate();
    103        const mockSocket = new MockSocket({
    104          write: (chunk) => {
    105            chunks.push(chunk);
    106            if (chunks.length !== 2) return;
    107 
    108            assert.strictEqual(chunks[0].length, 2);
    109            assert.notStrictEqual(chunk[0][0] & 0x40, 0x40);
    110            assert.strictEqual(chunks[1], 'hi');
    111            done();
    112          }
    113        });
    114        const sender = new Sender(mockSocket, {
    115          'permessage-deflate': perMessageDeflate
    116        });
    117        const extensions = extension.parse(
    118          'permessage-deflate; client_no_context_takeover'
    119        );
    120 
    121        perMessageDeflate.accept(extensions['permessage-deflate']);
    122 
    123        sender.send('hi', { compress: true, fin: true });
    124      });
    125 
    126      it('compresses all fragments of a fragmented message', (done) => {
    127        const chunks = [];
    128        const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
    129        const mockSocket = new MockSocket({
    130          write: (chunk) => {
    131            chunks.push(chunk);
    132            if (chunks.length !== 4) return;
    133 
    134            assert.strictEqual(chunks[0].length, 2);
    135            assert.strictEqual(chunks[0][0] & 0x40, 0x40);
    136            assert.strictEqual(chunks[1].length, 9);
    137 
    138            assert.strictEqual(chunks[2].length, 2);
    139            assert.strictEqual(chunks[2][0] & 0x40, 0x00);
    140            assert.strictEqual(chunks[3].length, 4);
    141            done();
    142          }
    143        });
    144        const sender = new Sender(mockSocket, {
    145          'permessage-deflate': perMessageDeflate
    146        });
    147        const extensions = extension.parse(
    148          'permessage-deflate; client_no_context_takeover'
    149        );
    150 
    151        perMessageDeflate.accept(extensions['permessage-deflate']);
    152 
    153        sender.send('123', { compress: true, fin: false });
    154        sender.send('12', { compress: true, fin: true });
    155      });
    156 
    157      it('does not compress any fragments of a fragmented message', (done) => {
    158        const chunks = [];
    159        const perMessageDeflate = new PerMessageDeflate({ threshold: 3 });
    160        const mockSocket = new MockSocket({
    161          write: (chunk) => {
    162            chunks.push(chunk);
    163            if (chunks.length !== 4) return;
    164 
    165            assert.strictEqual(chunks[0].length, 2);
    166            assert.strictEqual(chunks[0][0] & 0x40, 0x00);
    167            assert.strictEqual(chunks[1].length, 2);
    168 
    169            assert.strictEqual(chunks[2].length, 2);
    170            assert.strictEqual(chunks[2][0] & 0x40, 0x00);
    171            assert.strictEqual(chunks[3].length, 3);
    172            done();
    173          }
    174        });
    175        const sender = new Sender(mockSocket, {
    176          'permessage-deflate': perMessageDeflate
    177        });
    178        const extensions = extension.parse(
    179          'permessage-deflate; client_no_context_takeover'
    180        );
    181 
    182        perMessageDeflate.accept(extensions['permessage-deflate']);
    183 
    184        sender.send('12', { compress: true, fin: false });
    185        sender.send('123', { compress: true, fin: true });
    186      });
    187 
    188      it('compresses empty buffer as first fragment', (done) => {
    189        const chunks = [];
    190        const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
    191        const mockSocket = new MockSocket({
    192          write: (chunk) => {
    193            chunks.push(chunk);
    194            if (chunks.length !== 4) return;
    195 
    196            assert.strictEqual(chunks[0].length, 2);
    197            assert.strictEqual(chunks[0][0] & 0x40, 0x40);
    198            assert.strictEqual(chunks[1].length, 5);
    199 
    200            assert.strictEqual(chunks[2].length, 2);
    201            assert.strictEqual(chunks[2][0] & 0x40, 0x00);
    202            assert.strictEqual(chunks[3].length, 6);
    203            done();
    204          }
    205        });
    206        const sender = new Sender(mockSocket, {
    207          'permessage-deflate': perMessageDeflate
    208        });
    209        const extensions = extension.parse(
    210          'permessage-deflate; client_no_context_takeover'
    211        );
    212 
    213        perMessageDeflate.accept(extensions['permessage-deflate']);
    214 
    215        sender.send(Buffer.alloc(0), { compress: true, fin: false });
    216        sender.send('data', { compress: true, fin: true });
    217      });
    218 
    219      it('compresses empty buffer as last fragment', (done) => {
    220        const chunks = [];
    221        const perMessageDeflate = new PerMessageDeflate({ threshold: 0 });
    222        const mockSocket = new MockSocket({
    223          write: (chunk) => {
    224            chunks.push(chunk);
    225            if (chunks.length !== 4) return;
    226 
    227            assert.strictEqual(chunks[0].length, 2);
    228            assert.strictEqual(chunks[0][0] & 0x40, 0x40);
    229            assert.strictEqual(chunks[1].length, 10);
    230 
    231            assert.strictEqual(chunks[2].length, 2);
    232            assert.strictEqual(chunks[2][0] & 0x40, 0x00);
    233            assert.strictEqual(chunks[3].length, 1);
    234            done();
    235          }
    236        });
    237        const sender = new Sender(mockSocket, {
    238          'permessage-deflate': perMessageDeflate
    239        });
    240        const extensions = extension.parse(
    241          'permessage-deflate; client_no_context_takeover'
    242        );
    243 
    244        perMessageDeflate.accept(extensions['permessage-deflate']);
    245 
    246        sender.send('data', { compress: true, fin: false });
    247        sender.send(Buffer.alloc(0), { compress: true, fin: true });
    248      });
    249    });
    250  });
    251 
    252  describe('#ping', () => {
    253    it('works with multiple types of data', (done) => {
    254      const perMessageDeflate = new PerMessageDeflate();
    255      let count = 0;
    256      const mockSocket = new MockSocket({
    257        write: (data) => {
    258          if (++count < 3) return;
    259 
    260          if (count % 2) {
    261            assert.ok(data.equals(Buffer.from([0x89, 0x02])));
    262          } else if (count < 8) {
    263            assert.ok(data.equals(Buffer.from([0x68, 0x69])));
    264          } else {
    265            assert.strictEqual(data, 'hi');
    266            done();
    267          }
    268        }
    269      });
    270      const sender = new Sender(mockSocket, {
    271        'permessage-deflate': perMessageDeflate
    272      });
    273 
    274      perMessageDeflate.accept([{}]);
    275 
    276      const array = new Uint8Array([0x68, 0x69]);
    277 
    278      sender.send('foo', { compress: true, fin: true });
    279      sender.ping(array.buffer, false);
    280      sender.ping(array, false);
    281      sender.ping('hi', false);
    282    });
    283  });
    284 
    285  describe('#pong', () => {
    286    it('works with multiple types of data', (done) => {
    287      const perMessageDeflate = new PerMessageDeflate();
    288      let count = 0;
    289      const mockSocket = new MockSocket({
    290        write: (data) => {
    291          if (++count < 3) return;
    292 
    293          if (count % 2) {
    294            assert.ok(data.equals(Buffer.from([0x8a, 0x02])));
    295          } else if (count < 8) {
    296            assert.ok(data.equals(Buffer.from([0x68, 0x69])));
    297          } else {
    298            assert.strictEqual(data, 'hi');
    299            done();
    300          }
    301        }
    302      });
    303      const sender = new Sender(mockSocket, {
    304        'permessage-deflate': perMessageDeflate
    305      });
    306 
    307      perMessageDeflate.accept([{}]);
    308 
    309      const array = new Uint8Array([0x68, 0x69]);
    310 
    311      sender.send('foo', { compress: true, fin: true });
    312      sender.pong(array.buffer, false);
    313      sender.pong(array, false);
    314      sender.pong('hi', false);
    315    });
    316  });
    317 
    318  describe('#close', () => {
    319    it('throws an error if the first argument is invalid', () => {
    320      const mockSocket = new MockSocket();
    321      const sender = new Sender(mockSocket);
    322 
    323      assert.throws(
    324        () => sender.close('error'),
    325        /^TypeError: First argument must be a valid error code number$/
    326      );
    327 
    328      assert.throws(
    329        () => sender.close(1004),
    330        /^TypeError: First argument must be a valid error code number$/
    331      );
    332    });
    333 
    334    it('throws an error if the message is greater than 123 bytes', () => {
    335      const mockSocket = new MockSocket();
    336      const sender = new Sender(mockSocket);
    337 
    338      assert.throws(
    339        () => sender.close(1000, 'a'.repeat(124)),
    340        /^RangeError: The message must not be greater than 123 bytes$/
    341      );
    342    });
    343 
    344    it('should consume all data before closing', (done) => {
    345      const perMessageDeflate = new PerMessageDeflate();
    346 
    347      let count = 0;
    348      const mockSocket = new MockSocket({
    349        write: (data, cb) => {
    350          count++;
    351          if (cb) cb();
    352        }
    353      });
    354      const sender = new Sender(mockSocket, {
    355        'permessage-deflate': perMessageDeflate
    356      });
    357 
    358      perMessageDeflate.accept([{}]);
    359 
    360      sender.send('foo', { compress: true, fin: true });
    361      sender.send('bar', { compress: true, fin: true });
    362      sender.send('baz', { compress: true, fin: true });
    363 
    364      sender.close(1000, undefined, false, () => {
    365        assert.strictEqual(count, 8);
    366        done();
    367      });
    368    });
    369  });
    370 });