js-libp2p/test/switch/connection.node.js
Jacob Heun b294301456
refactor: add core modules to libp2p (#400)
* refactor: add js-libp2p-connection-manager to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Pedro Teixeira <i@pgte.me>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>

* test(conn-mgr): only run in node

* refactor: add js-libp2p-identify to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <hugomrdias@gmail.com>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Maciej Krüger <mkg20001@gmail.com>
Co-authored-by: Richard Littauer <richard.littauer@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
Co-authored-by: Yusef Napora <yusef@protocol.ai>
Co-authored-by: ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>

* refactor: add libp2p-pnet to repo

Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>

* refactor: add libp2p-ping to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Francisco Baio Dias <xicombd@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <mail@hugodias.me>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: João Antunes <j.goncalo.antunes@gmail.com>
Co-authored-by: Richard Littauer <richard.littauer@gmail.com>
Co-authored-by: Vasco Santos <vasco.santos@moxy.studio>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
Co-authored-by: ᴠɪᴄᴛᴏʀ ʙᴊᴇʟᴋʜᴏʟᴍ <victorbjelkholm@gmail.com>

* refactor: add libp2p-circuit to repo

Co-authored-by: David Dias <daviddias.p@gmail.com>
Co-authored-by: Dmitriy Ryajov <dryajov@gmail.com>
Co-authored-by: Friedel Ziegelmayer <dignifiedquire@gmail.com>
Co-authored-by: Hugo Dias <mail@hugodias.me>
Co-authored-by: Jacob Heun <jacobheun@gmail.com>
Co-authored-by: Maciej Krüger <mkg20001@gmail.com>
Co-authored-by: Oli Evans <oli@tableflip.io>
Co-authored-by: Pedro Teixeira <i@pgte.me>
Co-authored-by: Vasco Santos <vasco.santos@ua.pt>
Co-authored-by: Victor Bjelkholm <victorbjelkholm@gmail.com>
Co-authored-by: Yusef Napora <yusef@napora.org>
Co-authored-by: dirkmc <dirk@mccormick.cx>

* test(switch): avoid using instanceof

* chore(switch): update bignumber dep

* refactor(circuit): clean up tests

* refactor(switch): consolidate get peer utils

* test(identify): do deep checks of addresses

* test(identify): bump timeout for identify test

* test(switch): tidy up limit dialer test

* refactor(switch): remove redundant circuit tests

* chore: add coverage script

* refactor(circuit): consolidate get peer info

* docs: reference original repositories in each sub readme

* docs: fix comment

* refactor: clean up sub package.json files and readmes
2019-08-16 17:30:03 +02:00

452 lines
12 KiB
JavaScript

/* eslint-env mocha */
'use strict'
const chai = require('chai')
const expect = chai.expect
chai.use(require('dirty-chai'))
chai.use(require('chai-checkmark'))
const sinon = require('sinon')
const PeerBook = require('peer-book')
const WS = require('libp2p-websockets')
const parallel = require('async/parallel')
const secio = require('libp2p-secio')
const pull = require('pull-stream')
const multiplex = require('pull-mplex')
const spdy = require('libp2p-spdy')
const Protector = require('libp2p-pnet')
const generatePSK = Protector.generate
const psk = Buffer.alloc(95)
generatePSK(psk)
const ConnectionFSM = require('libp2p-switch/connection')
const Switch = require('libp2p-switch')
const createInfos = require('./utils').createInfos
describe('ConnectionFSM', () => {
let spdySwitch
let listenerSwitch
let dialerSwitch
before((done) => {
createInfos(3, (err, infos) => {
if (err) {
return done(err)
}
dialerSwitch = new Switch(infos.shift(), new PeerBook())
dialerSwitch._peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/15451/ws')
dialerSwitch.connection.crypto(secio.tag, secio.encrypt)
dialerSwitch.connection.addStreamMuxer(multiplex)
dialerSwitch.transport.add('ws', new WS())
listenerSwitch = new Switch(infos.shift(), new PeerBook())
listenerSwitch._peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/15452/ws')
listenerSwitch.connection.crypto(secio.tag, secio.encrypt)
listenerSwitch.connection.addStreamMuxer(multiplex)
listenerSwitch.transport.add('ws', new WS())
spdySwitch = new Switch(infos.shift(), new PeerBook())
spdySwitch._peerInfo.multiaddrs.add('/ip4/0.0.0.0/tcp/15453/ws')
spdySwitch.connection.crypto(secio.tag, secio.encrypt)
spdySwitch.connection.addStreamMuxer(spdy)
spdySwitch.transport.add('ws', new WS())
parallel([
(cb) => dialerSwitch.start(cb),
(cb) => listenerSwitch.start(cb),
(cb) => spdySwitch.start(cb)
], (err) => {
done(err)
})
})
})
after((done) => {
parallel([
(cb) => dialerSwitch.stop(cb),
(cb) => listenerSwitch.stop(cb),
(cb) => spdySwitch.stop(cb)
], () => {
done()
})
})
it('should have a default state of disconnected', () => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
expect(connection.getState()).to.equal('DISCONNECTED')
})
it('should emit an error with an invalid transition', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
expect(connection.getState()).to.equal('DISCONNECTED')
connection.once('error', (err) => {
expect(err).to.have.property('code', 'INVALID_STATE_TRANSITION')
done()
})
connection.upgrade()
})
it('.dial should create a basic connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
done()
})
connection.dial()
})
it('should be able to close with an error and not throw', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
expect(() => connection.close(new Error('shutting down'))).to.not.throw()
done()
})
connection.dial()
})
it('should emit warning on dial failed attempt', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
const stub = sinon.stub(dialerSwitch.transport, 'dial').callsArgWith(2, [
new Error('address in use')
])
connection.once('error:connection_attempt_failed', (errors) => {
expect(errors).to.have.length(1).mark()
stub.restore()
})
connection.once('error', (err) => {
expect(err).to.exist().mark()
})
expect(2).checks(done)
connection.dial()
})
it('should ignore concurrent dials', () => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
const stub = sinon.stub(connection, '_onDialing')
connection.dial()
connection.dial()
expect(stub.callCount).to.equal(1)
})
it('should be able to encrypt a basic connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
done()
})
connection.dial()
})
it('should disconnect on encryption failure', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
const stub = sinon.stub(dialerSwitch.crypto, 'encrypt')
.callsArgWith(3, new Error('fail encrypt'))
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('close', () => {
stub.restore()
done()
})
connection.once('encrypted', () => {
throw new Error('should not encrypt')
})
connection.dial()
})
it('should be able to upgrade an encrypted connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
connection.upgrade()
})
connection.once('muxed', (conn) => {
expect(conn.multicodec).to.equal(multiplex.multicodec)
done()
})
connection.dial()
})
it('should fail to upgrade a connection with incompatible muxers', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: spdySwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
connection.upgrade()
})
connection.once('error:upgrade_failed', (err) => {
expect(err).to.exist()
done()
})
connection.dial()
})
it('should be able to handshake a protocol over a muxed connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
listenerSwitch.handle('/muxed-conn-test/1.0.0', (_, conn) => {
return pull(conn, conn)
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
connection.upgrade()
})
connection.once('muxed', (conn) => {
expect(conn.multicodec).to.equal(multiplex.multicodec)
connection.shake('/muxed-conn-test/1.0.0', (err, protocolConn) => {
expect(err).to.not.exist()
expect(protocolConn).to.exist()
done()
})
})
connection.dial()
})
it('should not return a connection when handshaking with no protocol', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
listenerSwitch.handle('/muxed-conn-test/1.0.0', (_, conn) => {
return pull(conn, conn)
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
connection.upgrade()
})
connection.once('muxed', (conn) => {
expect(conn.multicodec).to.equal(multiplex.multicodec)
connection.shake(null, (err, protocolConn) => {
expect(err).to.not.exist()
expect(protocolConn).to.not.exist()
done()
})
})
connection.dial()
})
describe('with no muxers', () => {
let oldMuxers
before(() => {
oldMuxers = dialerSwitch.muxers
dialerSwitch.muxers = {}
})
after(() => {
dialerSwitch.muxers = oldMuxers
})
it('should be able to handshake a protocol over a basic connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
listenerSwitch.handle('/unmuxed-conn-test/1.0.0', (_, conn) => {
return pull(conn, conn)
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
connection.upgrade()
})
connection.once('muxed', () => {
throw new Error('connection shouldnt be muxed')
})
connection.once('unmuxed', (conn) => {
expect(conn).to.exist()
connection.shake('/unmuxed-conn-test/1.0.0', (err, protocolConn) => {
expect(err).to.not.exist()
expect(protocolConn).to.exist()
done()
})
})
connection.dial()
})
})
describe('with a protector', () => {
// Restart the switches with protectors
before((done) => {
parallel([
(cb) => dialerSwitch.stop(cb),
(cb) => listenerSwitch.stop(cb)
], () => {
dialerSwitch.protector = new Protector(psk)
listenerSwitch.protector = new Protector(psk)
parallel([
(cb) => dialerSwitch.start(cb),
(cb) => listenerSwitch.start(cb)
], done)
})
})
afterEach(() => {
sinon.restore()
})
it('should be able to protect a basic connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('private', (conn) => {
expect(conn).to.exist()
done()
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.protect()
})
connection.dial()
})
it('should close on failed protection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
const error = new Error('invalid key')
const stub = sinon.stub(dialerSwitch.protector, 'protect').callsFake((_, cb) => {
cb(error)
})
expect(3).check(done)
connection.once('close', () => {
expect(stub.callCount).to.eql(1).mark()
})
connection.once('error', (err) => {
expect(err).to.eql(error).mark()
})
connection.once('connected', (conn) => {
expect(conn).to.exist().mark()
connection.protect()
})
connection.dial()
})
it('should be able to encrypt a protected connection', (done) => {
const connection = new ConnectionFSM({
_switch: dialerSwitch,
peerInfo: listenerSwitch._peerInfo
})
connection.once('connected', (conn) => {
expect(conn).to.exist()
connection.protect()
})
connection.once('private', (conn) => {
expect(conn).to.exist()
connection.encrypt()
})
connection.once('encrypted', (conn) => {
expect(conn).to.exist()
done()
})
connection.dial()
})
})
})