UNPKG

@node-dlc/wire

Version:
464 lines (397 loc) 14.3 kB
// tslint:disable: max-classes-per-file // tslint:disable: no-unused-expression import { BitField } from '@node-dlc/common'; import { ILogger } from '@node-dlc/logger'; import * as noise from '@node-dlc/noise'; import { expect } from 'chai'; import sinon from 'sinon'; import { Duplex } from 'stream'; import { IWireMessage } from '../lib'; import { InitFeatureFlags } from '../lib/flags/InitFeatureFlags'; import { Peer } from '../lib/Peer'; import { PeerState } from '../lib/PeerState'; import { PingPongState } from '../lib/PingPongState'; import { createFakeLogger } from './_test-utils'; class FakeSocket extends Duplex { [x: string]: any; public outgoing: Buffer[] = []; constructor() { super(); this.end = sinon.spy(this.end.bind(this)); this.rpk = Buffer.alloc(33); } public _write( data: any, encoding: BufferEncoding, cb: (err?: Error) => void, ) { this.outgoing.push(data); cb(); } public _read() { // nada } public simReceive(data: Buffer) { this.push(data); } } class FakeMessage implements IWireMessage { public type = 3; [x: string]: any; constructor(msg) { this.msg = msg; } public serialize() { return Buffer.from(this.msg); } } describe('Peer', () => { let chainHashes: Buffer[]; let sut: Peer; let ls: Buffer; let rpk: Buffer; let logger: ILogger; let sandbox: sinon.SinonSandbox; let socket: FakeSocket; let localFeatures: BitField<InitFeatureFlags>; beforeEach(() => { chainHashes = [Buffer.alloc(32, 0xff)]; localFeatures = new BitField<InitFeatureFlags>(); localFeatures.set(InitFeatureFlags.optionDataLossProtectOptional); ls = Buffer.alloc(32, 0); rpk = Buffer.alloc(32, 1); logger = createFakeLogger(); sut = new Peer(ls, localFeatures, chainHashes, logger, 1); socket = new FakeSocket(); sut.pingPongState = sinon.createStubInstance(PingPongState) as any; sandbox = sinon.createSandbox(); }); afterEach(() => { sandbox.restore(); }); describe('.connect()', () => { beforeEach(() => { sandbox.stub(noise, 'connect').returns(socket as any); sandbox.stub(sut, '_onSocketReady' as any); sandbox.stub(sut, '_onSocketClose' as any); sandbox.stub(sut, '_onSocketError' as any); sandbox.stub(sut, '_onSocketReadable' as any); sut.connect(rpk, '127.0.0.1', 9735); }); it('should be initiator', () => { expect(sut.isInitiator).to.equal(true); }); it('should bind to ready', () => { socket.emit('ready'); expect((sut as any)._onSocketReady.called).to.be.true; }); it('should bind close', () => { socket.emit('close'); expect((sut as any)._onSocketClose.called).to.be.true; }); it('should bind error', () => { socket.emit('error'); expect((sut as any)._onSocketError.called).to.be.true; }); it('should bind readable', () => { socket.emit('readable'); expect((sut as any)._onSocketReadable.called).to.be.true; }); }); describe('.attach()', () => { beforeEach(() => { sandbox.stub(sut, '_onSocketReady' as any); sandbox.stub(sut, '_onSocketClose' as any); sandbox.stub(sut, '_onSocketError' as any); sandbox.stub(sut, '_onSocketReadable' as any); sut.attach(socket as any); }); it('should not be initiator', () => { expect(sut.isInitiator).to.equal(false); }); it('should bind to ready', () => { socket.emit('ready'); expect((sut as any)._onSocketReady.called).to.be.true; }); it('should bind close', () => { socket.emit('close'); expect((sut as any)._onSocketClose.called).to.be.true; }); it('should bind error', () => { socket.emit('error'); expect((sut as any)._onSocketError.called).to.be.true; }); it('should bind readable', () => { socket.emit('readable'); expect((sut as any)._onSocketReadable.called).to.be.true; }); }); describe('when connected', () => { beforeEach(() => { sut.attach(socket as any); }); describe('.sendMessage()', () => { it('should throw when not ready', () => { expect(() => sut.sendMessage(new FakeMessage('hello') as any), ).to.throw(); }); it('should send the serialized message', () => { const input = new FakeMessage('test'); sut.state = Peer.states.Ready; sut.sendMessage(input); expect((socket as any).outgoing[0]).to.deep.equal(Buffer.from('test')); }); it('should emit a sending message', (done) => { const input = new FakeMessage('test'); sut.state = Peer.states.Ready; sut.on('sending', () => done()); sut.sendMessage(input); }); }); describe('.disconnect()', () => { it('should stop the socket', () => { sut.disconnect(); expect((sut.socket as any).end.called).to.be.true; }); it('should change the peer state to disconnecting', () => { sut.disconnect(); expect(sut.state).to.equal(PeerState.Disconnecting); }); }); describe('.reconnect()', () => { it('should stop the socket', () => { sut.reconnect(); expect((sut.socket as any).end.called).to.be.true; }); it('should retain the peer state', () => { const beforeState = sut.state; sut.reconnect(); expect(sut.state).to.equal(beforeState); }); }); describe('._onSocketReady()', () => { it('should transition state to awaiting_peer_init', () => { (sut as any)._onSocketReady(); expect(sut.state).to.equal(Peer.states.AwaitingPeerInit); }); it('should send the init message to the peer', () => { (sut as any)._onSocketReady(); expect((socket as any).outgoing[0]).to.deep.equal( Buffer.from( '001000000001020120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex', ), ); }); }); describe('_onSocketClose', () => { it('should stop the ping pong state', () => { sut.state = PeerState.Disconnecting; (sut as any)._onSocketClose(); expect((sut as any).pingPongState.onDisconnecting.called).to.be.true; }); describe('when disconnecting', () => { it('should emit the close event', (done) => { sut.state = PeerState.Disconnecting; sut.on('close', () => done()); (sut as any)._onSocketClose(); }); }); describe('when initiator', () => { it('should trigger reconnect', (done) => { sut.state = PeerState.Ready; sut.reconnectTimeoutMs = 0; sut.connect = sandbox.stub(sut, 'connect'); sut.isInitiator = true; (sut as any)._onSocketClose(); setTimeout(() => { expect((sut.connect as any).called).to.be.true; done(); }, 50); }); }); describe('when not initiator', () => { it('should not trigger reconnect', (done) => { sut.state = PeerState.Ready; sut.reconnectTimeoutMs = 0; sut.connect = sandbox.stub(sut, 'connect'); sut.isInitiator = false; (sut as any)._onSocketClose(); setTimeout(() => { expect((sut.connect as any).called).to.be.false; done(); }, 50); }); }); }); describe('_onSocketError', () => { it('should emit error event', (done) => { sut.on('error', () => done()); (sut as any)._onSocketError(); }); }); describe('_sendInitMessage', () => { it('should send the initialization message', () => { (sut as any)._sendInitMessage(); expect((socket as any).outgoing[0]).to.deep.equal( Buffer.from( '001000000001020120ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex', ), ); }); }); describe('_processPeerInitMessage', () => { let input; beforeEach(() => { input = Buffer.from('001000000000', 'hex'); }); it('it should fail if not init message', () => { input = Buffer.from('001100000000', 'hex'); expect(() => (sut as any)._processPeerInitMessage(input)).to.throw(); }); it('should start ping state', () => { (sut as any)._processPeerInitMessage(input); expect((sut as any).pingPongState.start.called).to.be.true; }); it('should change the state to ready', () => { (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(Peer.states.Ready); }); it('should capture the init features', () => { input = Buffer.from('00100000000109', 'hex'); (sut as any)._processPeerInitMessage(input); expect(sut.remoteFeatures).to.be.instanceof(BitField); expect( sut.remoteFeatures.isSet( InitFeatureFlags.optionDataLossProtectRequired, ), ); expect( sut.remoteFeatures.isSet(InitFeatureFlags.initialRoutingSyncOptional), ); }); it('should emit ready', (done) => { sut.on('ready', () => done()); (sut as any)._processPeerInitMessage(input); }); it('should be ok with no remote chainhash', () => { input = Buffer.from('00100000000109', 'hex'); (sut as any)._processPeerInitMessage(input); }); it('should be ok with single chain_hash', () => { input = Buffer.from( '00100000000109' + '0120' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 'hex', ); (sut as any)._processPeerInitMessage(input); expect(sut.remoteChains.length).to.equal(1); expect(sut.remoteChains[0].toString('hex')).to.equal('ff'.repeat(32)); }); it('should be ok with multiple chain_hashes', () => { input = Buffer.from( '00100000000109' + '0140' + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' + 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 'hex', ); (sut as any)._processPeerInitMessage(input); expect(sut.remoteChains.length).to.equal(2); expect(sut.remoteChains[0].toString('hex')).to.equal('ff'.repeat(32)); expect(sut.remoteChains[1].toString('hex')).to.equal('ee'.repeat(32)); }); it('should disconnect with no chainhash match', () => { input = Buffer.from( '00100000000109' + '0120' + 'eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', 'hex', ); (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(PeerState.Disconnecting); }); it('should stay connected when local odd no remote feature', () => { // enable compulsory (bit 0) (sut as any).localFeatures = new BitField<InitFeatureFlags>(); sut.localFeatures.set(InitFeatureFlags.optionDataLossProtectOptional); // remote optional (bit 1) input = Buffer.from('00100000000100', 'hex'); (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(PeerState.Ready); }); it('should stay connected when local even/remote even feature', () => { // enable compulsory (bit 0) sut.localFeatures.set(InitFeatureFlags.optionDataLossProtectRequired); // remote optional (bit 1) input = Buffer.from('00100000000102', 'hex'); (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(PeerState.Ready); }); it('should stay connected with local even/remote even feature', () => { // enable compulsory (bit 0) sut.localFeatures.set(InitFeatureFlags.optionDataLossProtectRequired); // remote optional (bit 1) input = Buffer.from('00100000000101', 'hex'); (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(PeerState.Ready); }); it('should disconnect with local even missing remote feature', () => { // enable compulsory (bit 0) sut.localFeatures.set(InitFeatureFlags.optionDataLossProtectRequired); // remote optional (bit 1) input = Buffer.from('00100000000100', 'hex'); (sut as any)._processPeerInitMessage(input); expect(sut.state).to.equal(PeerState.Disconnecting); }); }); describe('_processMessage', () => { let input; let msg; beforeEach(() => { input = Buffer.from('001000000000', 'hex'); }); describe('when valid message', () => { it('should log with ping service', () => { msg = (sut as any)._processMessage(input); expect((sut as any).pingPongState.onMessage.called).to.be.true; }); it('should emit the message', () => { expect(msg.type).to.equal(16); }); }); }); describe('_onSocketReadable', () => { it('should read peer init message when awaiting_peer_init state', (done) => { sut.state = Peer.states.AwaitingPeerInit; sut.on('ready', () => done()); socket.simReceive(Buffer.from('001000000000', 'hex')); }); it('should process message when in ready state', (done) => { sut.state = Peer.states.Ready; sut.on('readable', () => { done(); }); socket.simReceive(Buffer.from('001000000000', 'hex')); }); describe('on error', () => { it('should close the socket', (done) => { sut.state = Peer.states.Ready; sut.on('error', () => { expect((socket as any).end.called).to.be.true; done(); }); socket.simReceive(Buffer.from('0010', 'hex')); }); it('should emit an error event', (done) => { sut.state = Peer.states.Ready; sut.on('error', () => done()); socket.simReceive(Buffer.from('0010', 'hex')); }); }); }); }); });