UNPKG

punchbuggy

Version:
431 lines (351 loc) 13.6 kB
const assert = require('assert') const dgram = require('dgram') const { getEventListeners } = require('events') const net = require('net') const fakeTimers = require('@sinonjs/fake-timers') const { cert, key } = require('./fixtures') const Client = require('../lib/client') const Server = require('../lib/server') const util = require('../lib/util') describe('integration', () => { beforeEach(async () => { this.clock = fakeTimers.install() this.client1 = new Client() this.client2 = new Client() this.client3 = new Client() this.server = new Server({ cert, key }) await this.server.listen() }) afterEach(() => { this.server.close() this.clock.uninstall() }) describe('client', () => { describe('#connectToServer()', () => { it('connects to server', async () => { await this.client1.connectToServer() assert(this.client1.serverSock instanceof net.Socket) assert(this.client1.udpSock instanceof dgram.Socket) }) it('errors if addr isn\'t an ipv4 address', async () => { try { await this.client1.connectToServer('127.0.-0.1') assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'First argument must be an IPv4 address') } }) it('errors again if addr isn\'t an ipv4 address', async () => { try { await this.client1.connectToServer('127.0.256.1') assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'First argument must be an IPv4 address') } }) }) describe('#handleServerMessage()', () => { it('errors if unexpected message from server', async () => { await this.client1.connectToServer() const msg = util.encode(util.MESSAGES.INFO_REQUEST, 0) const promise = new Promise((resolve, reject) => { this.client1.handleError = reject }) this.client1.serverSock.emit('data', msg) try { await promise assert.fail('Should reject') } catch ({ message }) { assert.strictEqual( message, `Unexpected message from server: code=${util.MESSAGE_CODES.INFO_REQUEST}, type=INFO_REQUEST` ) } }) }) describe('#sendToServer()', () => { beforeEach(async () => { await this.client1.connectToServer() }) it('sets nonce to 0 once it reaches max uint32', () => { this.client1.nonce = util.MAX_UINT32 this.client1.sendToServer(util.MESSAGES.ID_REQUEST) assert.strictEqual(this.client1.nonce, 0) }) }) describe('#requestId()', () => { beforeEach(async () => { await this.client1.connectToServer() }) it('requests session ID', async () => { await this.client1.requestId() const { sid } = this.client1 assert.strictEqual(typeof sid, 'string') assert.strictEqual(Buffer.from(sid, 'base64').byteLength, util.ID_LENGTH) }) it('issues multiple ID requests', async () => { await this.client1.requestId() try { await this.client1.requestId() assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Unexpected id request') } }) it('errors if server can\'t generate id', async () => { this.server.generateId = async () => { throw new Error('whoops') } try { await this.client1.requestId() assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Service unavailable') } }) it('errors if server can\'t generate different id', async () => { await Promise.all([ this.client1.requestId(), this.client2.connectToServer() ]) const buf = Buffer.from(this.client1.sid, 'base64') this.server.generateId = () => Promise.resolve(buf) try { await this.client2.requestId() assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Service unavailable') } }) }) describe('#requestInfo()', () => { beforeEach(async () => { await Promise.all([ this.client1.connectToServer(), this.client2.connectToServer(), this.client3.connectToServer() ]) await Promise.all([ this.client1.requestId(), this.client2.requestId(), this.client3.requestId() ]) }) it('requests own info', async () => { await Promise.all([ this.client1.requestInfo(), this.client2.requestInfo() ]) assert.strictEqual(this.client1.publicAddr, this.server.getSession(this.client1.sid).addr) assert.strictEqual(this.client1.publicPort, this.server.getSession(this.client1.sid).port) assert.strictEqual(this.client2.publicAddr, this.server.getSession(this.client2.sid).addr) assert.strictEqual(this.client2.publicPort, this.server.getSession(this.client2.sid).port) }) it('shares session info', async () => { await Promise.all([ this.client1.requestInfo(this.client2.sid), this.client2.requestInfo(this.client1.sid) ]) assert.strictEqual(this.client1.publicAddr, this.server.getSession(this.client1.sid).addr) assert.strictEqual(this.client1.publicPort, this.server.getSession(this.client1.sid).port) assert.strictEqual(this.client2.publicAddr, this.server.getSession(this.client2.sid).addr) assert.strictEqual(this.client2.publicPort, this.server.getSession(this.client2.sid).port) assert.strictEqual(this.client1.peerAddr, this.server.getSession(this.client2.sid).addr) assert.strictEqual(this.client1.peerPort, this.server.getSession(this.client2.sid).port) assert.strictEqual(this.client1.peerSid, this.client2.sid) assert.strictEqual(this.client2.peerAddr, this.server.getSession(this.client1.sid).addr) assert.strictEqual(this.client2.peerPort, this.server.getSession(this.client1.sid).port) assert.strictEqual(this.client2.peerSid, this.client1.sid) }) it('shares session info after packets dropped', async () => { const handleDatagram = this.server.handleDatagram.bind(this.server) this.server.handleDatagram = () => {} const promise1 = this.client1.requestInfo(this.client2.sid) const promise2 = this.client2.requestInfo(this.client1.sid) this.server.handleDatagram = handleDatagram this.clock.tick(util.RECEIVE_TIMEOUT) await Promise.all([promise1, promise2]) assert.strictEqual(this.client1.peerAddr, this.server.getSession(this.client2.sid).addr) assert.strictEqual(this.client1.peerPort, this.server.getSession(this.client2.sid).port) assert.strictEqual(this.client1.peerSid, this.client2.sid) assert.strictEqual(this.client2.peerAddr, this.server.getSession(this.client1.sid).addr) assert.strictEqual(this.client2.peerPort, this.server.getSession(this.client1.sid).port) assert.strictEqual(this.client2.peerSid, this.client1.sid) }) it('issues multiple INFO_REQUESTs', async () => { await Promise.all([ this.client1.requestInfo(this.client2.sid), this.client2.requestInfo(this.client1.sid) ]) try { await this.client1.requestInfo(this.client2.sid) assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Unexpected info request') } }) it('can\'t request own info', async () => { try { await this.client1.requestInfo(this.client1.sid) assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Must specify other session') } assert.strictEqual(this.client1.peerAddr, '') assert.strictEqual(this.client1.peerPort, 0) assert.strictEqual(this.client1.peerSid, '') }) it('can\'t find session', async () => { try { await this.client1.requestInfo('foobar') assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Session not found') } assert.strictEqual(this.client1.peerAddr, '') assert.strictEqual(this.client1.peerPort, 0) assert.strictEqual(this.client1.peerSid, '') }) it('can\'t request info for paired session', async () => { await Promise.all([ this.client2.requestInfo(this.client3.sid), this.client3.requestInfo(this.client2.sid) ]) try { await this.client1.requestInfo(this.client2.sid) assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Cannot return session info') } assert.strictEqual(this.client1.peerAddr, '') assert.strictEqual(this.client1.peerPort, 0) assert.strictEqual(this.client1.peerSid, '') }) }) describe('#dialPeer()', () => { beforeEach(async () => { await Promise.all([ this.client1.connectToServer(), this.client2.connectToServer() ]) await Promise.all([ this.client1.requestId(), this.client2.requestId() ]) await Promise.all([ this.client1.requestInfo(this.client2.sid), this.client2.requestInfo(this.client1.sid) ]) }) it('successfully dials between peers', async () => { await Promise.all([ this.client1.dialPeer(), this.client2.dialPeer() ]) assert.strictEqual(this.client1.dialInterval, null) assert.strictEqual(this.client2.dialInterval, null) }) it('times out on dial', async () => { const promise = this.client1.dialPeer() this.clock.tick(util.DIAL_TIMEOUT) try { await promise assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Dial timeout') } }) }) describe('#ejectUDPSocket()', () => { beforeEach(async () => { await Promise.all([ this.client1.connectToServer(), this.client2.connectToServer() ]) await Promise.all([ this.client1.requestId(), this.client2.requestId() ]) await Promise.all([ this.client1.requestInfo(this.client2.sid), this.client2.requestInfo(this.client1.sid) ]) await Promise.all([ this.client1.dialPeer(), this.client2.dialPeer() ]) }) it('ejects sockets after dialing', () => { const sock1 = this.client1.ejectUDPSocket() const sock2 = this.client2.ejectUDPSocket() assert(sock1 instanceof dgram.Socket) assert.deepStrictEqual(getEventListeners(sock1, 'message'), []) assert(sock2 instanceof dgram.Socket) assert.deepStrictEqual(getEventListeners(sock2, 'message'), []) }) it('creates new client from ejected socket', async () => { const sock = this.client1.ejectUDPSocket() const client = new Client(sock) await client.connectToServer() await client.requestId() await client.requestInfo() assert.strictEqual(client.localAddr, this.client1.localAddr) assert.strictEqual(client.localPort, this.client1.localPort) assert.strictEqual(client.publicAddr, this.client1.publicAddr) assert.strictEqual(client.publicPort, this.client1.publicPort) }) }) }) describe('session', () => { describe('#handleDialedRequest()', () => { beforeEach(async () => { await Promise.all([ this.client1.connectToServer(), this.client2.connectToServer() ]) await Promise.all([ this.client1.requestId(), this.client2.requestId() ]) await Promise.all([ this.client1.requestInfo(this.client2.sid), this.client2.requestInfo(this.client1.sid) ]) }) it('handles multiple DIALED_REQUESTs', async () => { try { await Promise.all([ this.client1.request(util.MESSAGES.DIALED_REQUEST), this.client1.request(util.MESSAGES.DIALED_REQUEST) ]) assert.fail('Should reject') } catch ({ message }) { assert.strictEqual(message, 'Unexpected dialed request') } }) }) describe('#handleError()', () => { beforeEach(async () => { await this.client1.connectToServer() await this.client1.requestId() }) it('handles unexpected message code', async () => { const msg = util.encode(util.MESSAGES.DIALED_RESPONSE, 0) const session = this.server.getSession(this.client1.sid) const promise = new Promise((resolve, reject) => { session.handleError = reject }) session.sock.emit('data', msg) try { await promise assert.fail('Should reject') } catch ({ message }) { assert.strictEqual( message, `Unexpected message from client: code=${util.MESSAGE_CODES.DIALED_RESPONSE}, type=DIALED_RESPONSE` ) } }) }) }) })