js-libp2p/test/circuit/v2/hop.spec.ts
2022-04-08 16:35:11 +02:00

279 lines
10 KiB
TypeScript

import { protocolIDv2Hop } from './../../../src/circuit/multicodec.js'
import { mockDuplex, mockConnection, mockMultiaddrConnection, mockStream } from '@libp2p/interface-compliance-tests/mocks'
import { expect } from 'aegir/utils/chai.js'
import * as peerUtils from '../../utils/creators/peer.js'
import { handleHopProtocol } from '../../../src/circuit/v2/hop.js'
import { StreamHandlerV2 } from '../../../src/circuit/v2/stream-handler.js'
import type { Connection } from '@libp2p/interfaces/connection'
import type { PeerId } from '@libp2p/interfaces/peer-id'
import { Status, HopMessage } from '../../../src/circuit/v2/pb/index.js'
import { ReservationStore } from '../../../src/circuit/v2/reservation-store.js'
import sinon from 'sinon'
import { Circuit } from '../../../src/circuit/transport.js'
import { Multiaddr } from '@multiformats/multiaddr'
import { pair } from 'it-pair'
/* eslint-env mocha */
describe('Circuit v2 - hop protocol', function () {
it('error on unknow message type', async function () {
const streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
await handleHopProtocol({
connection: mockConnection(mockMultiaddrConnection(mockDuplex(), await peerUtils.createPeerId())),
streamHandler,
request: {
// @ts-expect-error
type: 'not_existing'
}
})
const msg = HopMessage.decode(await streamHandler.read())
expect(msg.type).to.be.equal(HopMessage.Type.STATUS)
expect(msg.status).to.be.equal(Status.MALFORMED_MESSAGE)
})
describe('reserve', function () {
let relayPeer: PeerId, conn: Connection, streamHandler: StreamHandlerV2, reservationStore: ReservationStore
beforeEach(async () => {
[, relayPeer] = await peerUtils.createPeerIds(2)
conn = await mockConnection(mockMultiaddrConnection(mockDuplex(), relayPeer))
streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
reservationStore = new ReservationStore()
})
this.afterEach(async function () {
streamHandler.close()
await conn.close()
})
it('should reserve slot', async function () {
const expire: number = 123
const reserveStub = sinon.stub(reservationStore, 'reserve')
reserveStub.resolves({ status: Status.OK, expire })
await handleHopProtocol({
request: {
type: HopMessage.Type.RESERVE
},
connection: conn,
streamHandler,
relayPeer,
circuit: sinon.stub() as any,
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
reservationStore
})
expect(reserveStub.calledOnceWith(conn.remotePeer, conn.remoteAddr)).to.be.true()
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.limit).to.be.null()
expect(response.status).to.be.equal(Status.OK)
expect(response.reservation?.expire).to.be.equal(expire)
expect(response.reservation?.voucher).to.not.be.null()
expect(response.reservation?.addrs?.length).to.be.greaterThan(0)
})
it('should fail to reserve slot - acl denied', async function () {
const reserveStub = sinon.stub(reservationStore, 'reserve')
await handleHopProtocol({
request: {
type: HopMessage.Type.RESERVE
},
connection: conn,
streamHandler,
relayPeer,
circuit: sinon.stub() as any,
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
reservationStore,
acl: { allowReserve: async function () { return false }, allowConnect: sinon.stub() as any }
})
expect(reserveStub.notCalled).to.be.true()
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.limit).to.be.null()
expect(response.status).to.be.equal(Status.PERMISSION_DENIED)
})
it('should fail to reserve slot - resource exceeded', async function () {
const reserveStub = sinon.stub(reservationStore, 'reserve')
reserveStub.resolves({ status: Status.RESERVATION_REFUSED })
await handleHopProtocol({
request: {
type: HopMessage.Type.RESERVE
},
connection: conn,
streamHandler,
relayPeer,
circuit: sinon.stub() as any,
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
reservationStore
})
expect(reserveStub.calledOnce).to.be.true()
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.limit).to.be.null()
expect(response.status).to.be.equal(Status.RESERVATION_REFUSED)
})
it('should fail to reserve slot - failed to write response', async function () {
const reserveStub = sinon.stub(reservationStore, 'reserve')
const removeReservationStub = sinon.stub(reservationStore, 'removeReservation')
reserveStub.resolves({ status: Status.OK, expire: 123 })
removeReservationStub.resolves()
const backup = streamHandler.write
streamHandler.write = function () { throw new Error('connection reset') }
await handleHopProtocol({
request: {
type: HopMessage.Type.RESERVE
},
connection: conn,
streamHandler,
relayPeer,
circuit: sinon.stub() as any,
relayAddrs: [new Multiaddr('/ip4/127.0.0.1/udp/1234')],
reservationStore
})
expect(reserveStub.calledOnce).to.be.true()
expect(removeReservationStub.calledOnce).to.be.true()
streamHandler.write = backup
})
})
describe('connect', function () {
let relayPeer: PeerId, dstPeer: PeerId, conn: Connection, streamHandler: StreamHandlerV2, reservationStore: ReservationStore,
circuit: Circuit
beforeEach(async () => {
[, relayPeer, dstPeer] = await peerUtils.createPeerIds(3)
conn = await mockConnection(mockMultiaddrConnection(mockDuplex(), relayPeer))
streamHandler = new StreamHandlerV2({ stream: mockStream(pair<Uint8Array>()) })
reservationStore = new ReservationStore()
circuit = new Circuit({})
})
this.afterEach(async function () {
streamHandler.close()
await conn.close()
})
it('should succeed to connect', async function () {
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
hasReservationStub.resolves(true)
const dstConn = await mockConnection(
mockMultiaddrConnection(pair<Uint8Array>(), dstPeer)
)
const streamStub = sinon.stub(dstConn, 'newStream')
streamStub.resolves({ protocol: protocolIDv2Hop, stream: mockStream(pair<Uint8Array>()) })
const stub = sinon.stub(circuit, 'getPeerConnection')
stub.returns(dstConn)
await handleHopProtocol({
connection: conn,
streamHandler,
request: {
type: HopMessage.Type.CONNECT,
peer: {
id: dstPeer.toBytes(),
addrs: []
}
},
relayPeer: relayPeer,
relayAddrs: [],
reservationStore,
circuit
})
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.status).to.be.equal(Status.OK)
})
it('should fail to connect - invalid request', async function () {
await handleHopProtocol({
connection: conn,
streamHandler,
request: {
type: HopMessage.Type.CONNECT,
// @ts-expect-error
peer: {
}
},
reservationStore,
circuit
})
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.status).to.be.equal(Status.MALFORMED_MESSAGE)
})
it('should failed to connect - acl denied', async function () {
const acl = {
allowConnect: function () { return Status.PERMISSION_DENIED }
}
await handleHopProtocol({
connection: conn,
streamHandler,
request: {
type: HopMessage.Type.CONNECT,
peer: {
id: dstPeer.toBytes(),
addrs: []
}
},
reservationStore,
circuit,
// @ts-expect-error
acl
})
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.status).to.be.equal(Status.PERMISSION_DENIED)
})
it('should fail to connect - no reservation', async function () {
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
hasReservationStub.resolves(false)
await handleHopProtocol({
connection: conn,
streamHandler,
request: {
type: HopMessage.Type.CONNECT,
peer: {
id: dstPeer.toBytes(),
addrs: []
}
},
relayPeer: relayPeer,
relayAddrs: [],
reservationStore,
circuit
})
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.status).to.be.equal(Status.NO_RESERVATION)
})
it('should fail to connect - no connection', async function () {
const hasReservationStub = sinon.stub(reservationStore, 'hasReservation')
hasReservationStub.resolves(true)
const stub = sinon.stub(circuit, 'getPeerConnection')
stub.returns(undefined)
await handleHopProtocol({
connection: conn,
streamHandler,
request: {
type: HopMessage.Type.CONNECT,
peer: {
id: dstPeer.toBytes(),
addrs: []
}
},
relayPeer: relayPeer,
relayAddrs: [],
reservationStore,
circuit
})
const response = HopMessage.decode(await streamHandler.read())
expect(response.type).to.be.equal(HopMessage.Type.STATUS)
expect(response.status).to.be.equal(Status.NO_RESERVATION)
expect(stub.calledOnce).to.be.true()
})
})
})